changeset 1527:710e6250a401 zoom

Merge from default branch
author Chris Cannam
date Mon, 17 Sep 2018 13:51:14 +0100
parents d4a28d1479a8 (current diff) 8988b27ebf38 (diff)
children c1c45c5146bb
files base/BaseTypes.h base/ZoomConstraint.h data/fileio/QuickTimeFileReader.cpp data/fileio/QuickTimeFileReader.h data/fileio/test/testfiles/aac/32000-1.m4a data/fileio/test/testfiles/aac/44100-2.m4a data/fileio/test/testfiles/aiff/12000-6-16.aiff data/fileio/test/testfiles/aiff/48000-1-24.aiff data/fileio/test/testfiles/apple_lossless/32000-1.m4a data/fileio/test/testfiles/apple_lossless/44100-2.m4a data/fileio/test/testfiles/flac/44100-2.flac data/fileio/test/testfiles/mp3/32000-1.mp3 data/fileio/test/testfiles/mp3/44100-2.mp3 data/fileio/test/testfiles/ogg/32000-1.ogg data/fileio/test/testfiles/ogg/44100-2.ogg data/fileio/test/testfiles/wav/32000-1-16.wav data/fileio/test/testfiles/wav/44100-1-32.wav data/fileio/test/testfiles/wav/44100-2-16.wav data/fileio/test/testfiles/wav/44100-2-8.wav data/fileio/test/testfiles/wav/48000-1-16.wav data/fileio/test/testfiles/wav/8000-1-8.wav data/fileio/test/testfiles/wav/8000-2-16.wav data/fileio/test/testfiles/wav/8000-6-16.wav data/midi/rtmidi/RtError.h data/model/PowerOfSqrtTwoZoomConstraint.cpp data/model/PowerOfSqrtTwoZoomConstraint.h data/model/PowerOfTwoZoomConstraint.cpp data/model/PowerOfTwoZoomConstraint.h data/model/ReadOnlyWaveFileModel.cpp
diffstat 261 files changed, 12162 insertions(+), 6637 deletions(-) [+]
line wrap: on
line diff
--- a/base/AudioLevel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/AudioLevel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -31,7 +31,7 @@
 struct FaderDescription
 {
     FaderDescription(double _minDb, double _maxDb, double _zeroPoint) :
-	minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { }
+        minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { }
 
     double minDb;
     double maxDb;
@@ -97,17 +97,17 @@
     double db = 0.0f;
 
     if (def >= 50.0f) {
-	db = (def - 50.0f) / 2.5f - 20.0f;
+        db = (def - 50.0f) / 2.5f - 20.0f;
     } else if (def >= 30.0f) {
-	db = (def - 30.0f) / 2.0f - 30.0f;
+        db = (def - 30.0f) / 2.0f - 30.0f;
     } else if (def >= 15.0f) {
-	db = (def - 15.0f) / 1.5f - 40.0f;
+        db = (def - 15.0f) / 1.5f - 40.0f;
     } else if (def >= 7.5f) {
-	db = (def - 7.5f) / 0.75f - 50.0f;
+        db = (def - 7.5f) / 0.75f - 50.0f;
     } else if (def >= 2.5f) {
-	db = (def - 2.5f) / 0.5f - 60.0f;
+        db = (def - 2.5f) / 0.5f - 60.0f;
     } else {
-	db = (def / 0.25f) - 70.0f;
+        db = (def / 0.25f) - 70.0f;
     }
 
     return db;
@@ -120,32 +120,32 @@
 
     if (type == IEC268Meter || type == IEC268LongMeter) {
 
-	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;
+        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))
 
-	int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint);
+        int zeroLevel = int(round(maxLevel * faderTypes[type].zeroPoint));
     
-	if (level >= zeroLevel) {
-	    
-	    double value = level - zeroLevel;
-	    double scale = (maxLevel - zeroLevel) /
-		sqrt(faderTypes[type].maxDb);
-	    value /= scale;
-	    double dB = pow(value, 2.);
-	    return dB;
-	    
-	} else {
-	    
-	    double value = zeroLevel - level;
-	    double scale = zeroLevel / sqrt(0. - faderTypes[type].minDb);
-	    value /= scale;
-	    double dB = pow(value, 2.);
-	    return 0. - dB;
-	}
+        if (level >= zeroLevel) {
+            
+            double value = level - zeroLevel;
+            double scale = (maxLevel - zeroLevel) /
+                sqrt(faderTypes[type].maxDb);
+            value /= scale;
+            double dB = pow(value, 2.);
+            return dB;
+            
+        } else {
+            
+            double value = zeroLevel - level;
+            double scale = zeroLevel / sqrt(0. - faderTypes[type].minDb);
+            value /= scale;
+            double dB = pow(value, 2.);
+            return 0. - dB;
+        }
     }
 }
 
@@ -157,25 +157,25 @@
 
     if (type == IEC268Meter || type == IEC268LongMeter) {
 
-	// The IEC scale gives a "percentage travel" for a given dB
-	// level, but it reaches 100% at 0dB.  So we want to treat the
-	// result not as a percentage, but as a scale between 0 and
-	// whatever the "percentage" for our (possibly >0dB) max dB is.
-	
-	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;
-	if (faderLevel > maxLevel) faderLevel = maxLevel;
-	return faderLevel;
+        // The IEC scale gives a "percentage travel" for a given dB
+        // level, but it reaches 100% at 0dB.  So we want to treat the
+        // result not as a percentage, but as a scale between 0 and
+        // whatever the "percentage" for our (possibly >0dB) max dB is.
+        
+        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;
+        if (faderLevel > maxLevel) faderLevel = maxLevel;
+        return faderLevel;
 
     } else {
 
-	int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint);
+        int zeroLevel = int(round(maxLevel * faderTypes[type].zeroPoint));
 
-	if (dB >= 0.) {
-	    
+        if (dB >= 0.) {
+            
             if (faderTypes[type].maxDb <= 0.) {
                 
                 return maxLevel;
@@ -189,21 +189,21 @@
                 if (level > maxLevel) level = maxLevel;
                 return level;
             }
-	    
-	} else {
+            
+        } else {
 
-	    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;
-	    return level;
-	}
+            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;
+            return level;
+        }
     }
 }
 
-	
+        
 double
 AudioLevel::fader_to_multiplier(int level, int maxLevel, FaderType type)
 {
@@ -226,12 +226,12 @@
 {
     LevelList &ll = previewLevelCache[levels];
     if (ll.empty()) {
-	for (int i = 0; i <= levels; ++i) {
-	    double m = AudioLevel::fader_to_multiplier
-		(i + levels/4, levels + levels/4, AudioLevel::PreviewLevel);
-	    if (levels == 1) m /= 100; // noise
-	    ll.push_back(m);
-	}
+        for (int i = 0; i <= levels; ++i) {
+            double m = AudioLevel::fader_to_multiplier
+                (i + levels/4, levels + levels/4, AudioLevel::PreviewLevel);
+            if (levels == 1) m /= 100; // noise
+            ll.push_back(m);
+        }
     }
     return ll;
 }
@@ -255,23 +255,23 @@
     // binary search
     int level = -1;
     while (result < 0) {
-	int newlevel = (lo + hi) / 2;
-	if (newlevel == level ||
-	    newlevel == 0 ||
-	    newlevel == levels) {
-	    result = newlevel;
-	    break;
-	}
-	level = newlevel;
-	if (ll[level] >= m) {
-	    hi = level;
-	} else if (ll[level+1] >= m) {
-	    result = level;
-	} else {
-	    lo = level;
-	}
+        int newlevel = (lo + hi) / 2;
+        if (newlevel == level ||
+            newlevel == 0 ||
+            newlevel == levels) {
+            result = newlevel;
+            break;
+        }
+        level = newlevel;
+        if (ll[level] >= m) {
+            hi = level;
+        } else if (ll[level+1] >= m) {
+            result = level;
+        } else {
+            lo = level;
+        }
     }
-		   
+                   
     return result;
 
     */
@@ -288,5 +288,5 @@
     return ll[level];
 */
 }
-	
+        
 
--- a/base/AudioLevel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/AudioLevel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -38,11 +38,11 @@
     static const double DB_FLOOR;
 
     enum FaderType {
-	     ShortFader = 0, // -40 -> +6  dB
+             ShortFader = 0, // -40 -> +6  dB
               LongFader = 1, // -70 -> +10 dB
             IEC268Meter = 2, // -70 ->  0  dB
         IEC268LongMeter = 3, // -70 -> +10 dB (0dB aligns with LongFader)
-	   PreviewLevel = 4
+           PreviewLevel = 4
     };
 
     static double multiplier_to_dB(double multiplier);
@@ -53,7 +53,7 @@
 
     static double fader_to_multiplier(int level, int maxLevel, FaderType type);
     static int    multiplier_to_fader(double multiplier, int maxFaderLevel,
-				     FaderType type);
+                                     FaderType type);
 
     // fast if "levels" doesn't change often -- for audio segment previews
     static int    multiplier_to_preview(double multiplier, int levels);
--- a/base/AudioPlaySource.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/AudioPlaySource.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _AUDIO_PLAY_SOURCE_H_
-#define _AUDIO_PLAY_SOURCE_H_
+#ifndef SV_AUDIO_PLAY_SOURCE_H
+#define SV_AUDIO_PLAY_SOURCE_H
 
 #include "BaseTypes.h"
 
@@ -59,7 +59,9 @@
 
     /**
      * Return the current (or thereabouts) output levels in the range
-     * 0.0 -> 1.0, for metering purposes.
+     * 0.0 -> 1.0, for metering purposes.  The values returned are
+     * peak values since the last call to this function was made
+     * (i.e. calling this function also resets them).
      */
     virtual bool getOutputLevels(float &left, float &right) = 0;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/AudioRecordTarget.h	Mon Sep 17 13:51:14 2018 +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 SV_AUDIO_RECORD_TARGET_H
+#define SV_AUDIO_RECORD_TARGET_H
+
+#include "BaseTypes.h"
+
+/**
+ * The record target API used by the view manager. See also AudioPlaySource.
+ */
+class AudioRecordTarget
+{
+public:
+    virtual ~AudioRecordTarget() { }
+
+    /**
+     * Return whether recording is currently happening.
+     */
+    virtual bool isRecording() const = 0;
+
+    /**
+     * Return the approximate duration of the audio recording so far.
+     */
+    virtual sv_frame_t getRecordDuration() const = 0;
+
+    /**
+     * Return the current (or thereabouts) input levels in the range
+     * 0.0 -> 1.0, for metering purposes. Only valid while recording.
+     * The values returned are peak values since the last call to this
+     * function was made (i.e. calling this function also resets them).
+     */
+    virtual bool getInputLevels(float &left, float &right) = 0;
+};
+
+#endif
+
+
+
--- a/base/BaseTypes.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/BaseTypes.h	Mon Sep 17 13:51:14 2018 +0100
@@ -16,6 +16,10 @@
 #define SV_BASE_TYPES_H
 
 #include <cstdint>
+#include <complex>
+#include <vector>
+
+#include <bqvec/Allocators.h>
 
 /** Frame index, the unit of our time axis. This is signed because the
     axis conceptually extends below zero: zero represents the start of
@@ -34,9 +38,9 @@
 {
     if (i < 0) return false;
     if (sizeof(T) > sizeof(typename C::size_type)) {
-	return i < static_cast<T>(container.size());
+        return i < static_cast<T>(container.size());
     } else {
-	return static_cast<typename C::size_type>(i) < container.size();
+        return static_cast<typename C::size_type>(i) < container.size();
     }
 }
 
@@ -46,6 +50,10 @@
 */
 typedef double sv_samplerate_t;
 
+typedef std::vector<float, breakfastquay::StlAllocator<float>> floatvec_t;
+
+typedef std::vector<std::complex<float>,
+                    breakfastquay::StlAllocator<std::complex<float>>> complexvec_t;
 
 /** Display zoom level. Can be an integer number of samples per pixel,
  *  or an integer number of pixels per sample.
--- a/base/ColumnOp.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ColumnOp.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -49,10 +49,33 @@
     if (n == ColumnNormalization::None || in.empty()) {
         return in;
     }
+    
+    float shift = 0.f;
+    float scale = 1.f;
 
-    float scale = 1.f;
-        
-    if (n == ColumnNormalization::Sum1) {
+    if (n == ColumnNormalization::Range01) {
+
+        float min = 0.f;
+        float max = 0.f;
+        bool have = false;
+        for (auto v: in) {
+            if (v < min || !have) {
+                min = v;
+            }
+            if (v > max || !have) {
+                max = v;
+            }
+            have = true;
+        }
+        if (min != 0.f) {
+            shift = -min;
+            max -= min;
+        }
+        if (max != 0.f) {
+            scale = 1.f / max;
+        }
+
+    } else if (n == ColumnNormalization::Sum1) {
 
         float sum = 0.f;
 
@@ -86,7 +109,7 @@
         }
     }
 
-    return applyGain(in, scale);
+    return applyGain(applyShift(in, shift), scale);
 }
 
 ColumnOp::Column
--- a/base/ColumnOp.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ColumnOp.h	Mon Sep 17 13:51:14 2018 +0100
@@ -26,6 +26,9 @@
  * Max1 means to normalize to max value = 1.0.
  * Sum1 means to normalize to sum of values = 1.0.
  *
+ * Range01 means to normalize such that the max value = 1.0 and the
+ * min value (if different from the max value) = 0.0.
+ *
  * Hybrid means normalize to max = 1.0 and then multiply by
  * log10 of the max value, to retain some difference between
  * levels of neighbouring columns.
@@ -36,6 +39,7 @@
     None,
     Max1,
     Sum1,
+    Range01,
     Hybrid
 };
 
@@ -56,10 +60,21 @@
      */
     static Column applyGain(const Column &in, double gain) {
         if (gain == 1.0) return in;
-	Column out;
-	out.reserve(in.size());
-	for (auto v: in) out.push_back(float(v * gain));
-	return out;
+        Column out;
+        out.reserve(in.size());
+        for (auto v: in) out.push_back(float(v * gain));
+        return out;
+    }
+
+    /**
+     * Shift the values in the given column by the given offset.
+     */
+    static Column applyShift(const Column &in, float offset) {
+        if (offset == 0.f) return in;
+        Column out;
+        out.reserve(in.size());
+        for (auto v: in) out.push_back(v + offset);
+        return out;
     }
 
     /**
@@ -80,13 +95,13 @@
         if (!in_range_for(in, ix+1)) {
             return in[ix] > in[ix-1];
         }
-	if (in[ix] < in[ix+1]) {
+        if (in[ix] < in[ix+1]) {
             return false;
         }
-	if (in[ix] <= in[ix-1]) {
+        if (in[ix] <= in[ix-1]) {
             return false;
         }
-	return true;
+        return true;
     }
 
     /**
@@ -115,10 +130,10 @@
      * with the bin of index minbin.
      */
     static Column distribute(const Column &in,
-			     int h,
-			     const std::vector<double> &binfory,
-			     int minbin,
-			     bool interpolate);
+                             int h,
+                             const std::vector<double> &binfory,
+                             int minbin,
+                             bool interpolate);
 
 };
 
--- a/base/Command.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Command.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -24,7 +24,7 @@
 MacroCommand::~MacroCommand()
 {
     for (size_t i = 0; i < m_commands.size(); ++i) {
-	delete m_commands[i];
+        delete m_commands[i];
     }
 }
 
@@ -38,13 +38,13 @@
 MacroCommand::deleteCommand(Command *command)
 {
     for (std::vector<Command *>::iterator i = m_commands.begin();
-	 i != m_commands.end(); ++i) {
+         i != m_commands.end(); ++i) {
 
-	if (*i == command) {
-	    m_commands.erase(i);
-	    delete command;
-	    return;
-	}
+        if (*i == command) {
+            m_commands.erase(i);
+            delete command;
+            return;
+        }
     }
 }
 
@@ -58,7 +58,7 @@
 MacroCommand::execute()
 {
     for (size_t i = 0; i < m_commands.size(); ++i) {
-	m_commands[i]->execute();
+        m_commands[i]->execute();
     }
 }
 
@@ -66,7 +66,7 @@
 MacroCommand::unexecute()
 {
     for (size_t i = 0; i < m_commands.size(); ++i) {
-	m_commands[m_commands.size() - i - 1]->unexecute();
+        m_commands[m_commands.size() - i - 1]->unexecute();
     }
 }
 
--- a/base/HitCount.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/HitCount.h	Mon Sep 17 13:51:14 2018 +0100
@@ -25,34 +25,34 @@
 {
 public:
     HitCount(std::string name) :
-	m_name(name),
-	m_hit(0),
-	m_partial(0),
-	m_miss(0)
+        m_name(name),
+        m_hit(0),
+        m_partial(0),
+        m_miss(0)
     { }
     
     ~HitCount() {
 #ifndef NO_HIT_COUNTS
-	using namespace std;
-	int total = m_hit + m_partial + m_miss;
-	cerr << "Hit count: " << m_name << ": ";
-	if (m_partial > 0) {
-	    cerr << m_hit << " hits, " << m_partial << " partial, "
-		 << m_miss << " misses";
-	} else {
-	    cerr << m_hit << " hits, " << m_miss << " misses";
-	}
-	if (total > 0) {
-	    if (m_partial > 0) {
-		cerr << " (" << ((m_hit * 100.0) / total) << "%, "
-		     << ((m_partial * 100.0) / total) << "%, "
-		     << ((m_miss * 100.0) / total) << "%)";
-	    } else {
-		cerr << " (" << ((m_hit * 100.0) / total) << "%, "
-		     << ((m_miss * 100.0) / total) << "%)";
-	    }
-	}
-	cerr << endl;
+        using namespace std;
+        int total = m_hit + m_partial + m_miss;
+        cerr << "Hit count: " << m_name << ": ";
+        if (m_partial > 0) {
+            cerr << m_hit << " hits, " << m_partial << " partial, "
+                 << m_miss << " misses";
+        } else {
+            cerr << m_hit << " hits, " << m_miss << " misses";
+        }
+        if (total > 0) {
+            if (m_partial > 0) {
+                cerr << " (" << ((m_hit * 100.0) / total) << "%, "
+                     << ((m_partial * 100.0) / total) << "%, "
+                     << ((m_miss * 100.0) / total) << "%)";
+            } else {
+                cerr << " (" << ((m_hit * 100.0) / total) << "%, "
+                     << ((m_miss * 100.0) / total) << "%)";
+            }
+        }
+        cerr << endl;
 #endif
     }
 
--- a/base/LogRange.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/LogRange.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -23,30 +23,31 @@
 void
 LogRange::mapRange(double &min, double &max, double logthresh)
 {
+    static double eps = 1e-10;
+    
+    // ensure that max > min:
     if (min > max) std::swap(min, max);
     if (max == min) max = min + 1;
 
-//    cerr << "LogRange::mapRange: min = " << min << ", max = " << max << endl;
-    
-    if (min >= 0.f) {
+    if (min >= 0.0) {
 
-        max = log10(max); // we know max != 0
+        // and max > min, so we know min >= 0 and max > 0
+        
+        max = log10(max);
 
-        if (min == 0.f) min = std::min(logthresh, max);
+        if (min == 0.0) min = std::min(logthresh, max);
         else min = log10(min);
 
-//        cerr << "LogRange::mapRange: positive: min = " << min << ", max = " << max << endl;
+    } else if (max <= 0.0) {
 
-    } else if (max <= 0.f) {
+        // and max > min, so we know min < 0 and max <= 0
         
-        min = log10(-min); // we know min != 0
-        
-        if (max == 0.f) max = std::min(logthresh, min);
+        min = log10(-min);
+
+        if (max == 0.0) max = std::min(logthresh, min);
         else max = log10(-max);
         
         std::swap(min, max);
-        
-//        cerr << "LogRange::mapRange: negative: min = " << min << ", max = " << max << endl;
 
     } else {
         
@@ -54,17 +55,15 @@
         
         max = log10(std::max(max, -min));
         min = std::min(logthresh, max);
-
-//        cerr << "LogRange::mapRange: spanning: min = " << min << ", max = " << max << endl;
     }
 
-    if (min == max) min = max - 1;
+    if (fabs(max - min) < eps) min = max - 1;
 }        
 
 double
 LogRange::map(double value, double thresh)
 {
-    if (value == 0.f) return thresh;
+    if (value == 0.0) return thresh;
     return log10(fabs(value));
 }
 
@@ -77,7 +76,7 @@
 static double
 sd(const std::vector<double> &values, int start, int n)
 {
-    double sum = 0.f, mean = 0.f, variance = 0.f;
+    double sum = 0.0, mean = 0.0, variance = 0.0;
     for (int i = 0; i < n; ++i) {
         sum += values[start + i];
     }
@@ -91,7 +90,7 @@
 }
 
 bool
-LogRange::useLogScale(std::vector<double> values)
+LogRange::shouldUseLogScale(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
--- a/base/LogRange.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/LogRange.h	Mon Sep 17 13:51:14 2018 +0100
@@ -23,14 +23,6 @@
 {
 public:
     /**
-     * Map a linear range onto a logarithmic range.  min and max are
-     * passed as the extents of the linear range and returned as the
-     * 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(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.
@@ -44,11 +36,19 @@
     static double unmap(double value);
 
     /**
+     * Map a linear range onto a logarithmic range.  min and max are
+     * passed as the extents of the linear range and returned as the
+     * 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(double &min, double &max, double thresh = -10);
+
+    /**
      * 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<double> values);
+    static bool shouldUseLogScale(std::vector<double> values);
 
 };
 
--- a/base/MagnitudeRange.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/MagnitudeRange.h	Mon Sep 17 13:51:14 2018 +0100
@@ -29,7 +29,7 @@
     MagnitudeRange(float min, float max) : m_min(min), m_max(max) { }
     
     bool operator==(const MagnitudeRange &r) {
-	return r.m_min == m_min && r.m_max == m_max;
+        return r.m_min == m_min && r.m_max == m_max;
     }
     bool operator!=(const MagnitudeRange &r) {
         return !(*this == r);
@@ -37,20 +37,20 @@
     
     bool isSet() const { return (m_min != 0.f || m_max != 0.f); }
     void set(float min, float max) {
-	m_min = min;
-	m_max = max;
-	if (m_max < m_min) m_max = m_min;
+        m_min = min;
+        m_max = max;
+        if (m_max < m_min) m_max = m_min;
     }
     bool sample(float f) {
-	bool changed = false;
-	if (isSet()) {
-	    if (f < m_min) { m_min = f; changed = true; }
-	    if (f > m_max) { m_max = f; changed = true; }
-	} else {
-	    m_max = m_min = f;
-	    changed = true;
-	}
-	return changed;
+        bool changed = false;
+        if (isSet()) {
+            if (f < m_min) { m_min = f; changed = true; }
+            if (f > m_max) { m_max = f; changed = true; }
+        } else {
+            m_max = m_min = f;
+            changed = true;
+        }
+        return changed;
     }
     bool sample(const std::vector<float> &ff) {
         bool changed = false;
@@ -62,16 +62,16 @@
         return changed;
     }
     bool sample(const MagnitudeRange &r) {
-	bool changed = false;
-	if (isSet()) {
-	    if (r.m_min < m_min) { m_min = r.m_min; changed = true; }
-	    if (r.m_max > m_max) { m_max = r.m_max; changed = true; }
-	} else {
-	    m_min = r.m_min;
-	    m_max = r.m_max;
-	    changed = true;
-	}
-	return changed;
+        bool changed = false;
+        if (isSet()) {
+            if (r.m_min < m_min) { m_min = r.m_min; changed = true; }
+            if (r.m_max > m_max) { m_max = r.m_max; changed = true; }
+        } else {
+            m_min = r.m_min;
+            m_max = r.m_max;
+            changed = true;
+        }
+        return changed;
     }            
     float getMin() const { return m_min; }
     float getMax() const { return m_max; }
--- a/base/Pitch.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Pitch.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -21,8 +21,8 @@
 
 double
 Pitch::getFrequencyForPitch(int midiPitch,
-			    double centsOffset,
-			    double concertA)
+                            double centsOffset,
+                            double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
@@ -33,8 +33,8 @@
 
 int
 Pitch::getPitchForFrequency(double frequency,
-			    double *centsOffsetReturn,
-			    double concertA)
+                            double *centsOffsetReturn,
+                            double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
@@ -45,12 +45,12 @@
     double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
-	midiPitch = midiPitch + 1;
-	centsOffset = -(100.0 - centsOffset);
+        midiPitch = midiPitch + 1;
+        centsOffset = -(100.0 - centsOffset);
     }
     if (centsOffset < -50.0) {
-	midiPitch = midiPitch - 1;
-	centsOffset = (100.0 + centsOffset);
+        midiPitch = midiPitch - 1;
+        centsOffset = (100.0 + centsOffset);
     }
     
     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
@@ -80,8 +80,8 @@
     double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
-	midiPitch = midiPitch + 1;
-	centsOffset = -(100.0 - centsOffset);
+        midiPitch = midiPitch + 1;
+        centsOffset = -(100.0 - centsOffset);
     }
     
     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
@@ -120,12 +120,12 @@
     // spelled from a MIDI pitch + flats flag in isolation.
 
     if (midiPitch < 0) {
-	while (midiPitch < 0) {
-	    midiPitch += 12;
-	    --octave;
-	}
+        while (midiPitch < 0) {
+            midiPitch += 12;
+            --octave;
+        }
     } else {
-	octave = midiPitch / 12 + baseOctave;
+        octave = midiPitch / 12 + baseOctave;
     }
 
     note = midiPitch % 12;
@@ -133,8 +133,8 @@
 
 QString
 Pitch::getPitchLabel(int midiPitch,
-		     double centsOffset,
-		     bool useFlats)
+                     double centsOffset,
+                     bool useFlats)
 {
     int note, octave;
     getNoteAndOctaveForPitch(midiPitch, note, octave);
@@ -149,8 +149,8 @@
 
 QString
 Pitch::getPitchLabelForFrequency(double frequency,
-				 double concertA,
-				 bool useFlats)
+                                 double concertA,
+                                 bool useFlats)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
--- a/base/Pitch.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Pitch.h	Mon Sep 17 13:51:14 2018 +0100
@@ -31,8 +31,8 @@
      * specified in the application preferences (default 440Hz).
      */
     static double getFrequencyForPitch(int midiPitch,
-				      double centsOffset = 0,
-				      double concertA = 0.0);
+                                      double centsOffset = 0,
+                                      double concertA = 0.0);
 
     /**
      * Return the nearest MIDI pitch to the given frequency.
@@ -47,16 +47,16 @@
      * specified in the application preferences (default 440Hz).
      */
     static int getPitchForFrequency(double frequency,
-				    double *centsOffsetReturn = 0,
-				    double concertA = 0.0);
+                                    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) {
+                                    float *centsOffsetReturn,
+                                    double concertA = 0.0) {
         double c;
         int p = getPitchForFrequency(frequency, &c, concertA);
         if (centsOffsetReturn) *centsOffsetReturn = float(c);
@@ -127,8 +127,8 @@
      * e.g. Bb3 instead of A#3.
      */
     static QString getPitchLabel(int midiPitch,
-				 double centsOffset = 0,
-				 bool useFlats = false);
+                                 double centsOffset = 0,
+                                 bool useFlats = false);
     
     /**
      * Return a string describing the nearest MIDI pitch to the given
@@ -142,8 +142,8 @@
      * e.g. Bb3 instead of A#3.
      */
     static QString getPitchLabelForFrequency(double frequency,
-					     double concertA = 0.0,
-					     bool useFlats = false);
+                                             double concertA = 0.0,
+                                             bool useFlats = false);
 
     /**
      * Return a string describing the given pitch range in octaves,
--- a/base/PlayParameterRepository.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/PlayParameterRepository.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -39,8 +39,8 @@
 
     if (!getPlayParameters(playable)) {
 
-	// Give all playables the same type of play parameters for the
-	// moment
+        // Give all playables the same type of play parameters for the
+        // moment
 
 //        cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl;
 
@@ -121,8 +121,8 @@
 {
 //    cerr << "PlayParameterRepository: PlayParameterRepository::clear" << endl;
     while (!m_playParameters.empty()) {
-	delete m_playParameters.begin()->second;
-	m_playParameters.erase(m_playParameters.begin());
+        delete m_playParameters.begin()->second;
+        m_playParameters.erase(m_playParameters.begin());
     }
 }
 
--- a/base/Profiler.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Profiler.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -100,7 +100,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),
+                  (double)pp.first) / CLOCKS_PER_SEC),
                 int((double(pp.second.first) * 1000.0) / CLOCKS_PER_SEC));
 
         fprintf(stderr, "\tReal: \t%s ms      \t[%s ms total]\n",
@@ -192,8 +192,8 @@
     RealTime elapsedTime = RealTime::fromTimeval(tv) - m_startTime;
 
     cerr << "Profiler : id = " << m_c
-	 << " - elapsed so far = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC)
-	 << "ms CPU, " << elapsedTime << " real" << endl;
+         << " - elapsed so far = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC)
+         << "ms CPU, " << elapsedTime << " real" << endl;
 }    
 
 Profiler::~Profiler()
@@ -215,7 +215,7 @@
     if (m_showOnDestruct)
         cerr << "Profiler : id = " << m_c
              << " - elapsed = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC)
-	     << "ms CPU, " << elapsedTime << " real" << endl;
+             << "ms CPU, " << elapsedTime << " real" << endl;
 
     m_ended = true;
 }
--- a/base/ProgressReporter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ProgressReporter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -16,7 +16,7 @@
 #include "ProgressReporter.h"
 
 ProgressReporter::ProgressReporter(QObject *parent) :
-	QObject(parent)
+        QObject(parent)
 {
 }
 
--- a/base/ProgressReporter.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ProgressReporter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _PROGRESS_REPORTER_H_
-#define _PROGRESS_REPORTER_H_
+#ifndef SV_PROGRESS_REPORTER_H
+#define SV_PROGRESS_REPORTER_H
 
 #include <QObject>
 #include <QString>
--- a/base/PropertyContainer.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/PropertyContainer.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -178,6 +178,7 @@
 
     case ValueProperty:
     case ColourProperty:
+    case ColourMapProperty:
     {
         int min, max;
         getPropertyRangeAndValue(name, &min, &max, 0);
@@ -222,8 +223,8 @@
 }
 
 PropertyContainer::SetPropertyCommand::SetPropertyCommand(PropertyContainer *pc,
-							  const PropertyName &pn,
-							  int value) :
+                                                          const PropertyName &pn,
+                                                          int value) :
     m_pc(pc),
     m_pn(pn),
     m_value(value),
--- a/base/PropertyContainer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/PropertyContainer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _PROPERTY_CONTAINER_H_
-#define _PROPERTY_CONTAINER_H_
+#ifndef SV_PROPERTY_CONTAINER_H
+#define SV_PROPERTY_CONTAINER_H
 
 #include "Command.h"
 
@@ -36,12 +36,13 @@
     typedef std::vector<PropertyName> PropertyList;
     
     enum PropertyType {
-	ToggleProperty, // on or off
-	RangeProperty, // range of integers
-	ValueProperty, // range of integers given string labels
-	ColourProperty, // colours, get/set as ColourDatabase indices
+        ToggleProperty, // on or off
+        RangeProperty, // range of integers
+        ValueProperty, // range of integers given string labels
+        ColourProperty, // colours, get/set as ColourDatabase indices
+        ColourMapProperty, // colour maps, get/set as ColourMapper::StandardMap enum
         UnitsProperty, // unit from UnitDatabase, get/set unit id
-	InvalidProperty, // property not found!
+        InvalidProperty, // property not found!
     };
 
     /**
@@ -81,14 +82,14 @@
      * passed as NULL if their values are not required.
      */
     virtual int getPropertyRangeAndValue(const PropertyName &,
-					 int *min, int *max, int *deflt) const;
+                                         int *min, int *max, int *deflt) const;
 
     /**
      * If the given property is a ValueProperty, return the display
      * label to be used for the given value for that property.
      */
     virtual QString getPropertyValueLabel(const PropertyName &,
-					  int value) const;
+                                          int value) const;
 
     /**
      * If the given property is a ValueProperty, return the icon to be
@@ -158,18 +159,18 @@
     class SetPropertyCommand : public Command
     {
     public:
-	SetPropertyCommand(PropertyContainer *pc, const PropertyName &pn, int);
-	virtual ~SetPropertyCommand() { }
+        SetPropertyCommand(PropertyContainer *pc, const PropertyName &pn, int);
+        virtual ~SetPropertyCommand() { }
 
-	virtual void execute();
-	virtual void unexecute();
-	virtual QString getName() const;
+        virtual void execute();
+        virtual void unexecute();
+        virtual QString getName() const;
 
     protected:
-	PropertyContainer *m_pc;
-	PropertyName m_pn;
-	int m_value;
-	int m_oldValue;
+        PropertyContainer *m_pc;
+        PropertyName m_pn;
+        int m_value;
+        int m_oldValue;
     };
 
     virtual bool convertPropertyStrings(QString nameString, QString valueString,
--- a/base/RangeMapper.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/RangeMapper.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -22,7 +22,7 @@
 #include <iostream>
 
 LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
-				     double minval, double maxval,
+                                     double minval, double maxval,
                                      QString unit, bool inverted,
                                      std::map<int, QString> labels) :
     m_minpos(minpos),
--- a/base/RangeMapper.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/RangeMapper.h	Mon Sep 17 13:51:14 2018 +0100
@@ -129,7 +129,7 @@
 
     static void convertMinMax(int minpos, int maxpos,
                               double minval, double maxval,
-                              double &ratio, double &minlog);
+                              double &minlog, double &ratio);
 
     virtual int getPositionForValue(double value) const;
     virtual int getPositionForValueUnclamped(double value) const;
--- a/base/RealTime.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/RealTime.h	Mon Sep 17 13:51:14 2018 +0100
@@ -51,10 +51,10 @@
     RealTime(int s, int n);
 
     RealTime(const RealTime &r) :
-	sec(r.sec), nsec(r.nsec) { }
+        sec(r.sec), nsec(r.nsec) { }
 
     RealTime(const Vamp::RealTime &r) :
-	sec(r.sec), nsec(r.nsec) { }
+        sec(r.sec), nsec(r.nsec) { }
 
     static RealTime fromSeconds(double sec);
     static RealTime fromMilliseconds(int msec);
@@ -65,27 +65,27 @@
     Vamp::RealTime toVampRealTime() const { return Vamp::RealTime(sec, nsec); }
 
     RealTime &operator=(const RealTime &r) {
-	sec = r.sec; nsec = r.nsec; return *this;
+        sec = r.sec; nsec = r.nsec; return *this;
     }
 
     RealTime operator+(const RealTime &r) const {
-	return RealTime(sec + r.sec, nsec + r.nsec);
+        return RealTime(sec + r.sec, nsec + r.nsec);
     }
     RealTime operator-(const RealTime &r) const {
-	return RealTime(sec - r.sec, nsec - r.nsec);
+        return RealTime(sec - r.sec, nsec - r.nsec);
     }
     RealTime operator-() const {
-	return RealTime(-sec, -nsec);
+        return RealTime(-sec, -nsec);
     }
 
     bool operator <(const RealTime &r) const {
-	if (sec == r.sec) return nsec < r.nsec;
-	else return sec < r.sec;
+        if (sec == r.sec) return nsec < r.nsec;
+        else return sec < r.sec;
     }
 
     bool operator >(const RealTime &r) const {
-	if (sec == r.sec) return nsec > r.nsec;
-	else return sec > r.sec;
+        if (sec == r.sec) return nsec > r.nsec;
+        else return sec > r.sec;
     }
 
     bool operator==(const RealTime &r) const {
--- a/base/RealTimeSV.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/RealTimeSV.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -19,6 +19,7 @@
 */
 
 #include <iostream>
+#include <limits.h>
 
 #include <cstdlib>
 #include <sstream>
@@ -43,16 +44,10 @@
 RealTime::RealTime(int s, int n) :
     sec(s), nsec(n)
 {
-    if (sec == 0) {
-	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
-	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
-    } else if (sec < 0) {
-	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
-	while (nsec > 0 && sec < 0)  { nsec -= ONE_BILLION; ++sec; }
-    } else { 
-	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
-	while (nsec < 0 && sec > 0)  { nsec += ONE_BILLION; --sec; }
-    }
+    while (nsec <= -ONE_BILLION && sec > INT_MIN) { nsec += ONE_BILLION; --sec; }
+    while (nsec >=  ONE_BILLION && sec < INT_MAX) { nsec -= ONE_BILLION; ++sec; }
+    while (nsec > 0 && sec < 0) { nsec -= ONE_BILLION; ++sec; }
+    while (nsec < 0 && sec > 0) { nsec += ONE_BILLION; --sec; }
 }
 
 RealTime
@@ -174,9 +169,9 @@
 std::ostream &operator<<(std::ostream &out, const RealTime &rt)
 {
     if (rt < RealTime::zeroTime) {
-	out << "-";
+        out << "-";
     } else {
-	out << " ";
+        out << " ";
     }
 
     int s = (rt.sec < 0 ? -rt.sec : rt.sec);
@@ -187,8 +182,8 @@
     int nn(n);
     if (nn == 0) out << "00000000";
     else while (nn < (ONE_BILLION / 10)) {
-	out << "0";
-	nn *= 10;
+        out << "0";
+        nn *= 10;
     }
     
     out << n << "R";
@@ -319,24 +314,24 @@
     int ms = msec();
 
     if (ms != 0) {
-	out << ".";
-	out << (ms / 100);
-	ms = ms % 100;
-	if (ms != 0) {
-	    out << (ms / 10);
-	    ms = ms % 10;
-	} else if (fixedDp) {
-	    out << "0";
-	}
-	if (ms != 0) {
-	    out << ms;
-	} else if (fixedDp) {
-	    out << "0";
-	}
+        out << ".";
+        out << (ms / 100);
+        ms = ms % 100;
+        if (ms != 0) {
+            out << (ms / 10);
+            ms = ms % 10;
+        } else if (fixedDp) {
+            out << "0";
+        }
+        if (ms != 0) {
+            out << ms;
+        } else if (fixedDp) {
+            out << "0";
+        }
     } else if (fixedDp) {
-	out << ".000";
+        out << ".000";
     }
-	
+        
     std::string s = out.str();
 
     return s;
@@ -371,7 +366,7 @@
         out << d;
         div /= 10;
     }
-	
+        
     std::string s = out.str();
 
 //    cerr << "converted " << toString() << " to " << s << endl;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/RecordDirectory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,67 @@
+/* -*- 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 "RecordDirectory.h"
+#include "TempDirectory.h"
+
+#include <QDir>
+#include <QDateTime>
+
+#include "Debug.h"
+
+QString
+RecordDirectory::getRecordContainerDirectory()
+{
+    QDir parent(TempDirectory::getInstance()->getContainingPath());
+    QString subdirname("recorded");
+
+    if (!parent.mkpath(subdirname)) {
+        SVCERR << "ERROR: RecordDirectory::getRecordContainerDirectory: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+QString
+RecordDirectory::getRecordDirectory()
+{
+    QDir parent(getRecordContainerDirectory());
+    QDateTime now = QDateTime::currentDateTime();
+    QString subdirname = QString("%1").arg(now.toString("yyyyMMdd"));
+
+    if (!parent.mkpath(subdirname)) {
+        SVCERR << "ERROR: RecordDirectory::getRecordDirectory: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+QString
+RecordDirectory::getConvertedAudioDirectory()
+{
+    QDir parent(getRecordContainerDirectory());
+    QDateTime now = QDateTime::currentDateTime();
+    QString subdirname = "converted";
+
+    if (!parent.mkpath(subdirname)) {
+        SVCERR << "ERROR: RecordDirectory::getConvertedAudioDirectory: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/RecordDirectory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,59 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_RECORD_DIRECTORY_H
+#define SV_RECORD_DIRECTORY_H
+
+#include <QString>
+
+/**
+ * Report the intended target location for recorded audio files.
+ */
+class RecordDirectory
+{
+public:
+    /**
+     * Return the directory in which a recorded file should be saved.
+     * This may vary depending on the current date and time, and so
+     * should be queried afresh for each recording. The directory will
+     * also be created if it does not yet exist.
+     *
+     * Returns an empty string if the record directory did not exist
+     * and could not be created.
+     */
+    static QString getRecordDirectory();
+
+    /**
+     * Return the root "recorded files" directory. If
+     * getRecordDirectory() is returning a datestamped directory, then
+     * this will be its parent. The directory will also be created if
+     * it does not yet exist.
+     *
+     * Returns an empty string if the record directory did not exist
+     * and could not be created.
+     */
+    static QString getRecordContainerDirectory();
+
+    /**
+     * Return the directory in which an audio file converted from a
+     * data file should be saved. The directory will also be created if
+     * it does not yet exist.
+     *
+     * Returns an empty string if the directory did not exist and
+     * could not be created.
+     */
+    static QString getConvertedAudioDirectory();
+};
+
+#endif
--- a/base/ResourceFinder.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ResourceFinder.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -35,6 +35,8 @@
 #include <iostream>
 #include <stdexcept>
 
+#include "system/System.h"
+
 /**
    Resource files may be found in three places:
 
@@ -67,10 +69,11 @@
     QStringList list;
 
 #ifdef Q_OS_WIN32
-    char *programFiles = getenv("ProgramFiles");
-    if (programFiles && programFiles[0]) {
+    std::string programFiles;
+    (void)getEnvUtf8("ProgramFiles", programFiles);
+    if (programFiles != "") {
         list << QString("%1/%2/%3")
-            .arg(programFiles)
+            .arg(QString::fromStdString(programFiles))
             .arg(qApp->organizationName())
             .arg(qApp->applicationName());
     } else {
@@ -133,14 +136,22 @@
     }
 
 #if QT_VERSION >= 0x050000
+
     // This is expected to be much more reliable than
     // getOldStyleUserResourcePrefix(), but it returns a different
     // directory because it includes the organisation name (which is
     // fair enough). Hence migrateOldStyleResources() which moves
     // across any resources found in the old-style path the first time
     // we look for the new-style one
+#if QT_VERSION >= 0x050400
     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
 #else
+    cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with older version of Qt (pre 5.4), resource location may be incompatible with future versions" << endl;
+    return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+#endif
+    
+#else
+    cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with very old version of Qt (pre 5.0?), resource location may be incompatible with future versions" << endl;
     return getOldStyleUserResourcePrefix();
 #endif
 }
@@ -239,12 +250,12 @@
         
         QString prefix = *i;
 
-        SVDEBUG << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl;
+//        cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl;
 
         QString path =
             QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
         if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
-            cerr << "Found it!" << endl;
+//            cerr << "Found it!" << endl;
             return path;
         }
     }
--- a/base/ResourceFinder.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ResourceFinder.h	Mon Sep 17 13:51:14 2018 +0100
@@ -25,7 +25,7 @@
 #include <QString>
 
 #include "Debug.h"
-	
+        
 class ResourceFinder
 {
 public:
--- a/base/RingBuffer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/RingBuffer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -210,7 +210,7 @@
     delete[] m_readers;
 
     if (m_mlocked) {
-	MUNLOCK((void *)m_buffer, m_size * sizeof(T));
+        MUNLOCK((void *)m_buffer, m_size * sizeof(T));
     }
     delete[] m_buffer;
 }
@@ -293,15 +293,15 @@
 {
     int space = 0;
     for (int i = 0; i < N; ++i) {
-	int here = (m_readers[i] + m_size - m_writer - 1) % m_size;
-	if (i == 0 || here < space) space = here;
+        int here = (m_readers[i] + m_size - m_writer - 1) % m_size;
+        if (i == 0 || here < space) space = here;
     }
 
 #ifdef DEBUG_RINGBUFFER
     int rs(getReadSpace()), rp(m_readers[0]);
 
     std::cerr << "RingBuffer: write space " << space << ", read space "
-	      << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl;
+              << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl;
     std::cerr << "RingBuffer: reader " << rp << ", writer " << m_writer << std::endl;
 #endif
 
@@ -323,20 +323,20 @@
     int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only " << available << " samples available"
-		  << std::endl;
+        std::cerr << "WARNING: Only " << available << " samples available"
+                  << std::endl;
 #endif
-	memset(destination + available, 0, (n - available) * sizeof(T));
-	n = available;
+        memset(destination + available, 0, (n - available) * sizeof(T));
+        n = available;
     }
     if (n == 0) return n;
 
     int here = m_size - m_readers[R];
     if (here >= n) {
-	memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+        memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
     } else {
-	memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
-	memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+        memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+        memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
     }
 
     MBARRIER();
@@ -360,26 +360,26 @@
     int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only " << available << " samples available"
-		  << std::endl;
+        std::cerr << "WARNING: Only " << available << " samples available"
+                  << std::endl;
 #endif
-	n = available;
+        n = available;
     }
     if (n == 0) return n;
 
     int here = m_size - m_readers[R];
 
     if (here >= n) {
-	for (int i = 0; i < n; ++i) {
-	    destination[i] += (m_buffer + m_readers[R])[i];
-	}
+        for (int i = 0; i < n; ++i) {
+            destination[i] += (m_buffer + m_readers[R])[i];
+        }
     } else {
-	for (int i = 0; i < here; ++i) {
-	    destination[i] += (m_buffer + m_readers[R])[i];
-	}
-	for (int i = 0; i < (n - here); ++i) {
-	    destination[i + here] += m_buffer[i];
-	}
+        for (int i = 0; i < here; ++i) {
+            destination[i] += (m_buffer + m_readers[R])[i];
+        }
+        for (int i = 0; i < (n - here); ++i) {
+            destination[i + here] += m_buffer[i];
+        }
     }
 
     MBARRIER();
@@ -397,12 +397,12 @@
 
     if (m_writer == m_readers[R]) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: No sample available"
-		  << std::endl;
+        std::cerr << "WARNING: No sample available"
+                  << std::endl;
 #endif
-	T t;
-	memset(&t, 0, sizeof(T));
-	return t;
+        T t;
+        memset(&t, 0, sizeof(T));
+        return t;
     }
     T value = m_buffer[m_readers[R]];
     MBARRIER();
@@ -421,20 +421,20 @@
     int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only " << available << " samples available"
-		  << std::endl;
+        std::cerr << "WARNING: Only " << available << " samples available"
+                  << std::endl;
 #endif
-	memset(destination + available, 0, (n - available) * sizeof(T));
-	n = available;
+        memset(destination + available, 0, (n - available) * sizeof(T));
+        n = available;
     }
     if (n == 0) return n;
 
     int here = m_size - m_readers[R];
     if (here >= n) {
-	memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+        memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
     } else {
-	memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
-	memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+        memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+        memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
     }
 
 #ifdef DEBUG_RINGBUFFER
@@ -454,12 +454,12 @@
 
     if (m_writer == m_readers[R]) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: No sample available"
-		  << std::endl;
+        std::cerr << "WARNING: No sample available"
+                  << std::endl;
 #endif
-	T t;
-	memset(&t, 0, sizeof(T));
-	return t;
+        T t;
+        memset(&t, 0, sizeof(T));
+        return t;
     }
     T value = m_buffer[m_readers[R]];
     return value;
@@ -476,10 +476,10 @@
     int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only " << available << " samples available"
-		  << std::endl;
+        std::cerr << "WARNING: Only " << available << " samples available"
+                  << std::endl;
 #endif
-	n = available;
+        n = available;
     }
     if (n == 0) return n;
     m_readers[R] = (m_readers[R] + n) % m_size;
@@ -497,19 +497,19 @@
     int available = getWriteSpace();
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only room for " << available << " samples"
-		  << std::endl;
+        std::cerr << "WARNING: Only room for " << available << " samples"
+                  << std::endl;
 #endif
-	n = available;
+        n = available;
     }
     if (n == 0) return n;
 
     int here = m_size - m_writer;
     if (here >= n) {
-	memcpy(m_buffer + m_writer, source, n * sizeof(T));
+        memcpy(m_buffer + m_writer, source, n * sizeof(T));
     } else {
-	memcpy(m_buffer + m_writer, source, here * sizeof(T));
-	memcpy(m_buffer, source + here, (n - here) * sizeof(T));
+        memcpy(m_buffer + m_writer, source, here * sizeof(T));
+        memcpy(m_buffer, source + here, (n - here) * sizeof(T));
     }
 
     MBARRIER();
@@ -533,19 +533,19 @@
     int available = getWriteSpace();
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
-	std::cerr << "WARNING: Only room for " << available << " samples"
-		  << std::endl;
+        std::cerr << "WARNING: Only room for " << available << " samples"
+                  << std::endl;
 #endif
-	n = available;
+        n = available;
     }
     if (n == 0) return n;
 
     int here = m_size - m_writer;
     if (here >= n) {
-	memset(m_buffer + m_writer, 0, n * sizeof(T));
+        memset(m_buffer + m_writer, 0, n * sizeof(T));
     } else {
-	memset(m_buffer + m_writer, 0, here * sizeof(T));
-	memset(m_buffer, 0, (n - here) * sizeof(T));
+        memset(m_buffer + m_writer, 0, here * sizeof(T));
+        memset(m_buffer, 0, (n - here) * sizeof(T));
     }
     
     MBARRIER();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/ScaleTickIntervals.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,385 @@
+/* -*- 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-2017 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 SV_SCALE_TICK_INTERVALS_H
+#define SV_SCALE_TICK_INTERVALS_H
+
+#include <string>
+#include <vector>
+#include <cmath>
+
+#include "LogRange.h"
+#include "Debug.h"
+
+// Can't have this on by default, as we're called on every refresh
+//#define DEBUG_SCALE_TICK_INTERVALS 1
+
+class ScaleTickIntervals
+{
+public:
+    struct Range {
+        double min;        // start of value range
+        double max;        // end of value range
+        int n;             // number of divisions (approximate only)
+    };
+
+    struct Tick {
+        double value;      // value this tick represents
+        std::string label; // value as written 
+    };
+
+    typedef std::vector<Tick> Ticks;
+
+    /**
+     * Return a set of ticks that divide the range r linearly into
+     * roughly r.n equal divisions, in such a way as to yield
+     * reasonably human-readable labels.
+     */
+    static Ticks linear(Range r) {
+        return linearTicks(r);
+    }
+
+    /**
+     * Return a set of ticks that divide the range r into roughly r.n
+     * logarithmic divisions, in such a way as to yield reasonably
+     * human-readable labels.
+     */
+    static Ticks logarithmic(Range r) {
+        LogRange::mapRange(r.min, r.max);
+        return logarithmicAlready(r);
+    }
+
+    /**
+     * Return a set of ticks that divide the range r into roughly r.n
+     * logarithmic divisions, on the asssumption that r.min and r.max
+     * already represent the logarithms of the boundary values rather
+     * than the values themselves.
+     */
+    static Ticks logarithmicAlready(Range r) {
+        return logTicks(r);
+    }
+    
+private:
+    enum Display {
+        Fixed,
+        Scientific,
+        Auto
+    };
+    
+    struct Instruction {
+        double initial;    // value of first tick
+        double limit;      // max from original range
+        double spacing;    // increment between ticks
+        double roundTo;    // what all displayed values should be rounded to
+                           // (if 0.0, then calculate based on precision)
+        Display display;   // whether to use fixed precision (%e, %f, or %g)
+        int precision;     // number of dp (%f) or sf (%e)
+        bool logUnmap;     // true if values represent logs of display values
+    };
+    
+    static Instruction linearInstruction(Range r)
+    {
+        Display display = Auto;
+
+        if (r.max < r.min) {
+            return linearInstruction({ r.max, r.min, r.n });
+        }
+        if (r.n < 1 || r.max == r.min) {
+            return { r.min, r.min, 1.0, r.min, display, 1, false };
+        }
+        
+        double inc = (r.max - r.min) / r.n;
+
+        double digInc = log10(inc);
+        double digMax = log10(fabs(r.max));
+        double digMin = log10(fabs(r.min));
+
+        int precInc = int(floor(digInc));
+        double roundTo = pow(10.0, precInc);
+
+        if (precInc > -4 && precInc < 4) {
+            display = Fixed;
+        } else if ((digMax >= -2.0 && digMax <= 3.0) &&
+                   (digMin >= -3.0 && digMin <= 3.0)) {
+            display = Fixed;
+        } else {
+            display = Scientific;
+        }
+        
+        int precRange = int(ceil(digMax - digInc));
+
+        int prec = 1;
+        
+        if (display == Fixed) {
+            if (digInc < 0) {
+                prec = -precInc;
+            } else {
+                prec = 0;
+            }
+        } else {
+            prec = precRange;
+        }
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals: calculating linearInstruction" << endl
+                << "ScaleTickIntervals: min = " << r.min << ", max = " << r.max
+                << ", n = " << r.n << ", inc = " << inc << endl;
+        SVDEBUG << "ScaleTickIntervals: digMax = " << digMax
+                << ", digInc = " << digInc << endl;
+        SVDEBUG << "ScaleTickIntervals: display = " << display
+                << ", inc = " << inc << ", precInc = " << precInc
+                << ", precRange = " << precRange
+                << ", prec = " << prec << ", roundTo = " << roundTo
+                << endl;
+#endif
+
+        double min = r.min;
+        
+        if (roundTo != 0.0) {
+            // Round inc to the nearest multiple of roundTo, and min
+            // to the next multiple of roundTo up. The small offset of
+            // eps is included to avoid inc of 2.49999999999 rounding
+            // to 2 or a min of -0.9999999999 rounding to 0, both of
+            // which would prevent some of our test cases from getting
+            // the most natural results.
+            double eps = 1e-7;
+            inc = round(inc / roundTo + eps) * roundTo;
+            if (inc < roundTo) inc = roundTo;
+            min = ceil(min / roundTo - eps) * roundTo;
+            if (min > r.max) min = r.max;
+            if (min == -0.0) min = 0.0;
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+            SVDEBUG << "ScaleTickIntervals: rounded inc to " << inc
+                    << " and min to " << min << endl;
+#endif
+        }
+
+        if (display == Scientific && min != 0.0) {
+            double digNewMin = log10(fabs(min));
+            if (digNewMin < digInc) {
+                prec = int(ceil(digMax - digNewMin));
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+                SVDEBUG << "ScaleTickIntervals: min is smaller than increment, adjusting prec to " << prec << endl;
+#endif
+            }
+        }
+        
+        return { min, r.max, inc, roundTo, display, prec, false };
+    }
+    
+    static Instruction logInstruction(Range r)
+    {
+        Display display = Auto;
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: Range is "
+                << r.min << " to " << r.max << endl;
+#endif
+        
+        if (r.n < 1) {
+            return {};
+        }
+        if (r.max < r.min) {
+            return logInstruction({ r.max, r.min, r.n });
+        }
+        if (r.max == r.min) {
+            return { r.min, r.max, 1.0, r.min, display, 1, true };
+        }
+        
+        double inc = (r.max - r.min) / r.n;
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                << "Naive increment is " << inc << endl;
+#endif
+
+        int precision = 1;
+
+        if (inc < 1.0) {
+            precision = int(ceil(1.0 - inc)) + 1;
+        }
+
+        double digInc = log10(inc);
+        int precInc = int(floor(digInc));
+        double roundIncTo = pow(10.0, precInc);
+
+        inc = round(inc / roundIncTo) * roundIncTo;
+        if (inc < roundIncTo) inc = roundIncTo;
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                << "Rounded increment to " << inc << endl;
+#endif
+
+        // if inc is close to giving us powers of two, nudge it
+        if (fabs(inc - 0.301) < 0.01) {
+            inc = log10(2.0);
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+            SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                    << "Nudged increment to " << inc << " to get powers of two"
+                    << endl;
+#endif
+        }
+
+        double min = r.min;
+        if (inc != 0.0) {
+            min = ceil(r.min / inc) * inc;
+            if (min > r.max) min = r.max;
+        }
+
+        return { min, r.max, inc, 0.0, display, precision, true };
+    }
+
+    static Ticks linearTicks(Range r) {
+        Instruction instruction = linearInstruction(r);
+        Ticks ticks = explode(instruction);
+        return ticks;
+    }
+
+    static Ticks logTicks(Range r) {
+        Instruction instruction = logInstruction(r);
+        Ticks ticks = explode(instruction);
+        return ticks;
+    }
+    
+    static Tick makeTick(Display display, int precision, double value) {
+
+        if (value == -0.0) {
+            value = 0.0;
+        }
+        
+        const int buflen = 40;
+        char buffer[buflen];
+
+        if (display == Auto) {
+
+            double eps = 1e-7;
+            
+            int digits = (value != 0.0 ?
+                          1 + int(floor(eps + log10(fabs(value)))) :
+                          0);
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+            SVDEBUG << "makeTick: display = Auto, precision = "
+                    << precision << ", value = " << value
+                    << ", resulting digits = " << digits << endl;
+#endif
+            
+            // This is not the same logic as %g uses for determining
+            // whether to delegate to use scientific or fixed notation
+
+            if (digits < -3 || digits > 4) {
+
+                display = Auto; // delegate planning to %g
+
+            } else {
+
+                display = Fixed;
+                
+                // in %.*f, the * indicates decimal places, not sig figs
+                if (precision >= digits) {
+                    precision -= digits;
+                } else {
+                    precision = 0;
+                }
+            }
+        }
+
+        const char *spec = (display == Auto ? "%.*g" :
+                            display == Scientific ? "%.*e" :
+                            "%.*f");
+
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+        
+        snprintf(buffer, buflen, spec, precision, value);
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "makeTick: spec = \"" << spec
+                << "\", prec = " << precision << ", value = " << value
+                << ", label = \"" << buffer << "\"" << endl;
+#endif
+        
+        return Tick({ value, std::string(buffer) });
+    }
+    
+    static Ticks explode(Instruction instruction) {
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::explode:" << endl
+                << "initial = " << instruction.initial
+                << ", limit = " << instruction.limit
+                << ", spacing = " << instruction.spacing
+                << ", roundTo = " << instruction.roundTo
+                << ", display = " << instruction.display
+                << ", precision = " << instruction.precision
+                << ", logUnmap = " << instruction.logUnmap
+                << endl;
+#endif
+
+        if (instruction.spacing == 0.0) {
+            return {};
+        }
+
+        double eps = 1e-7;
+        if (instruction.spacing < eps * 10.0) {
+            eps = instruction.spacing / 10.0;
+        }
+
+        double max = instruction.limit;
+        int n = 0;
+
+        Ticks ticks;
+        
+        while (true) {
+
+            double value = instruction.initial + n * instruction.spacing;
+
+            if (value >= max + eps) {
+                break;
+            }
+
+            if (instruction.logUnmap) {
+                value = pow(10.0, value);
+            }
+
+            double roundTo = instruction.roundTo;
+
+            if (roundTo == 0.0 && value != 0.0) {
+                // We don't want the internal value secretly not
+                // matching the displayed one
+                roundTo =
+                    pow(10, ceil(log10(fabs(value))) - instruction.precision);
+            }
+                                           
+            if (roundTo != 0.0) {
+                value = roundTo * round(value / roundTo);
+            }
+
+            if (fabs(value) < eps) {
+                value = 0.0;
+            }
+            
+            ticks.push_back(makeTick(instruction.display,
+                                     instruction.precision,
+                                     value));
+            ++n;
+        }
+
+        return ticks;
+    }
+};
+
+#endif
--- a/base/Scavenger.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Scavenger.h	Mon Sep 17 13:51:14 2018 +0100
@@ -106,15 +106,15 @@
 Scavenger<T>::~Scavenger()
 {
     if (m_scavenged < m_claimed) {
-	for (size_t i = 0; i < m_objects.size(); ++i) {
-	    ObjectTimePair &pair = m_objects[i];
-	    if (pair.first != 0) {
-		T *ot = pair.first;
-		pair.first = 0;
-		delete ot;
-		++m_scavenged;
-	    }
-	}
+        for (size_t i = 0; i < m_objects.size(); ++i) {
+            ObjectTimePair &pair = m_objects[i];
+            if (pair.first != 0) {
+                T *ot = pair.first;
+                pair.first = 0;
+                delete ot;
+                ++m_scavenged;
+            }
+        }
     }
 
     clearExcess(0);
@@ -131,17 +131,17 @@
     time_t sec = tv.tv_sec;
 
     for (size_t i = 0; i < m_objects.size(); ++i) {
-	ObjectTimePair &pair = m_objects[i];
-	if (pair.first == 0) {
-	    pair.second = sec;
-	    pair.first = t;
-	    ++m_claimed;
-	    return;
-	}
+        ObjectTimePair &pair = m_objects[i];
+        if (pair.first == 0) {
+            pair.second = sec;
+            pair.first = t;
+            ++m_claimed;
+            return;
+        }
     }
 
     std::cerr << "WARNING: Scavenger::claim(" << t << "): run out of slots, "
-	      << "using non-RT-safe method" << std::endl;
+              << "using non-RT-safe method" << std::endl;
     pushExcess(t);
 }
 
@@ -158,14 +158,14 @@
     time_t sec = tv.tv_sec;
 
     for (size_t i = 0; i < m_objects.size(); ++i) {
-	ObjectTimePair &pair = m_objects[i];
-	if (clearNow ||
-	    (pair.first != 0 && pair.second + m_sec < sec)) {
-	    T *ot = pair.first;
-	    pair.first = 0;
-	    delete ot;
-	    ++m_scavenged;
-	}
+        ObjectTimePair &pair = m_objects[i];
+        if (clearNow ||
+            (pair.first != 0 && pair.second + m_sec < sec)) {
+            T *ot = pair.first;
+            pair.first = 0;
+            delete ot;
+            ++m_scavenged;
+        }
     }
 
     if (sec > m_lastExcess + m_sec) {
@@ -191,8 +191,8 @@
 {
     m_excessMutex.lock();
     for (typename ObjectList::iterator i = m_excess.begin();
-	 i != m_excess.end(); ++i) {
-	delete *i;
+         i != m_excess.end(); ++i) {
+        delete *i;
     }
     m_excess.clear();
     m_lastExcess = sec;
--- a/base/Selection.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Selection.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -27,9 +27,9 @@
     m_endFrame(endFrame)
 {
     if (m_startFrame > m_endFrame) {
-	sv_frame_t tmp = m_endFrame;
-	m_endFrame = m_startFrame;
-	m_startFrame = tmp;
+        sv_frame_t tmp = m_endFrame;
+        m_endFrame = m_startFrame;
+        m_startFrame = tmp;
     }
 }
 
@@ -43,8 +43,8 @@
 Selection::operator=(const Selection &s)
 {
     if (this != &s) {
-	m_startFrame = s.m_startFrame;
-	m_endFrame = s.m_endFrame;
+        m_startFrame = s.m_startFrame;
+        m_endFrame = s.m_endFrame;
     } 
     return *this;
 }
@@ -81,11 +81,11 @@
 Selection::operator<(const Selection &s) const
 {
     if (isEmpty()) {
-	if (s.isEmpty()) return false;
-	else return true;
+        if (s.isEmpty()) return false;
+        else return true;
     } else {
-	if (s.isEmpty()) return false;
-	else return (m_startFrame < s.m_startFrame);
+        if (s.isEmpty()) return false;
+        else return (m_startFrame < s.m_startFrame);
     }
 }
 
@@ -95,7 +95,7 @@
     if (isEmpty()) return s.isEmpty();
 
     return (m_startFrame == s.m_startFrame &&
-	    m_endFrame == s.m_endFrame);
+            m_endFrame == s.m_endFrame);
 }
 
 
@@ -134,21 +134,21 @@
     // this is not just a frill.
 
     for (SelectionList::iterator i = m_selections.begin();
-	 i != m_selections.end(); ) {
-	
-	SelectionList::iterator j = i;
-	if (++j == m_selections.end()) break;
+         i != m_selections.end(); ) {
+        
+        SelectionList::iterator j = i;
+        if (++j == m_selections.end()) break;
 
-	if (i->getEndFrame() >= j->getStartFrame()) {
-	    Selection merged(i->getStartFrame(),
-			     std::max(i->getEndFrame(), j->getEndFrame()));
-	    m_selections.erase(i);
-	    m_selections.erase(j);
-	    m_selections.insert(merged);
-	    i = m_selections.begin();
-	} else {
-	    ++i;
-	}
+        if (i->getEndFrame() >= j->getStartFrame()) {
+            Selection merged(i->getStartFrame(),
+                             std::max(i->getEndFrame(), j->getEndFrame()));
+            m_selections.erase(i);
+            m_selections.erase(j);
+            m_selections.insert(merged);
+            i = m_selections.begin();
+        } else {
+            ++i;
+        }
     }
 }
 
@@ -161,7 +161,7 @@
     //appropriately)
 
     if (m_selections.find(selection) != m_selections.end()) {
-	m_selections.erase(selection);
+        m_selections.erase(selection);
     }
 }
 
@@ -169,7 +169,7 @@
 MultiSelection::clearSelections()
 {
     if (!m_selections.empty()) {
-	m_selections.clear();
+        m_selections.clear();
     }
 }
 
@@ -180,7 +180,7 @@
     endFrame = 0;
     
     for (SelectionList::const_iterator i = m_selections.begin();
-	 i != m_selections.end(); ++i) {
+         i != m_selections.end(); ++i) {
 
         if (i == m_selections.begin() || i->getStartFrame() < startFrame) {
             startFrame = i->getStartFrame();
@@ -200,14 +200,14 @@
     // scalable method, and I think that may be what we need
 
     for (SelectionList::const_iterator i = m_selections.begin();
-	 i != m_selections.end(); ++i) {
+         i != m_selections.end(); ++i) {
 
-	if (i->contains(frame)) return *i;
+        if (i->contains(frame)) return *i;
 
-	if (i->getStartFrame() > frame) {
-	    if (defaultToFollowing) return *i;
-	    else return Selection();
-	}
+        if (i->getStartFrame() > frame) {
+            if (defaultToFollowing) return *i;
+            else return Selection();
+        }
     }
 
     return Selection();
@@ -219,10 +219,10 @@
 {
     stream << indent << QString("<selections %1>\n").arg(extraAttributes);
     for (SelectionList::iterator i = m_selections.begin();
-	 i != m_selections.end(); ++i) {
-	stream << indent
+         i != m_selections.end(); ++i) {
+        stream << indent
                << QString("  <selection start=\"%1\" end=\"%2\"/>\n")
-	    .arg(i->getStartFrame()).arg(i->getEndFrame());
+            .arg(i->getStartFrame()).arg(i->getEndFrame());
     }
     stream << indent << "</selections>\n";
 }
--- a/base/Selection.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Selection.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _SELECTION_H_
-#define _SELECTION_H_
+#ifndef SV_SELECTION_H
+#define SV_SELECTION_H
 
 #include <cstddef>
 #include <set>
--- a/base/StorageAdviser.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/StorageAdviser.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -65,8 +65,8 @@
 
 StorageAdviser::Recommendation
 StorageAdviser::recommend(Criteria criteria,
-			  size_t minimumSize,
-			  size_t maximumSize)
+                          size_t minimumSize,
+                          size_t maximumSize)
 {
     SVDEBUG << "StorageAdviser::recommend: criteria " << criteria
             << " (" + criteriaToString(criteria) + ")"
@@ -83,7 +83,7 @@
     QString path;
     try {
         path = TempDirectory::getInstance()->getPath();
-    } catch (std::exception e) {
+    } catch (const std::exception &e) {
         SVDEBUG << "StorageAdviser::recommend: ERROR: Failed to get temporary directory path: " << e.what() << endl;
         int r = UseMemory | ConserveSpace;
         SVDEBUG << "StorageAdviser: returning fallback " << r
@@ -97,6 +97,28 @@
     SVDEBUG << "StorageAdviser: disc space: " << discFree
             << "M, memory free: " << memoryFree
             << "M, memory total: " << memoryTotal << "M" << endl;
+
+    // In 32-bit addressing mode we can't address more than 4Gb.
+    // If the total memory is reported as more than 4Gb, we should
+    // reduce the available amount by the difference between 4Gb
+    // and the total. This won't give us an accurate idea of the
+    // amount of memory available any more, but it should be enough
+    // to prevent us from trying to allocate more for our own use
+    // than can be addressed at all!
+    if (sizeof(void *) < 8) {
+        if (memoryTotal > 4096) {
+            ssize_t excess = memoryTotal - 4096;
+            if (memoryFree > excess) {
+                memoryFree -= excess;
+            } else {
+                memoryFree = 0;
+            }
+            SVDEBUG << "StorageAdviser: more real memory found than we "
+                    << "can address in a 32-bit process, reducing free "
+                    << "estimate to " << memoryFree << "M accordingly" << endl;
+        }
+    }
+
     SVDEBUG << "StorageAdviser: disc planned: " << (m_discPlanned / 1024)
             << "K, memory planned: " << (m_memoryPlanned / 1024) << "K" << endl;
     SVDEBUG << "StorageAdviser: min requested: " << minimumSize
--- a/base/StringBits.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/StringBits.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -42,7 +42,7 @@
 
     while (i < len) {
 
-	QChar c = s[i];
+        QChar c = s[i];
 
         if (c.isDigit()) {
 
@@ -85,45 +85,45 @@
     enum { sep, unq, q1, q2 } mode = sep;
 
     for (int i = 0; i < s.length(); ++i) {
-	
-	QChar c = s[i];
+        
+        QChar c = s[i];
 
-	if (c == '\'') {
-	    switch (mode) {
-	    case sep: mode = q1; break;
-	    case unq: case q2: tok += c; break;
-	    case q1: mode = unq; break;
-	    }
+        if (c == '\'') {
+            switch (mode) {
+            case sep: mode = q1; break;
+            case unq: case q2: tok += c; 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 = unq; break;
-	    }
+        } else if (c == '"') {
+            switch (mode) {
+            case sep: mode = q2; break;
+            case unq: case q1: tok += c; break;
+            case q2: mode = unq; break;
+            }
 
-	} else if (c == separator || (separator == ' ' && c.isSpace())) {
-	    switch (mode) {
-	    case sep: if (separator != ' ') tokens << ""; break;
-	    case unq: mode = sep; tokens << tok; tok = ""; break;
-	    case q1: case q2: tok += c; break;
-	    }
+        } else if (c == separator || (separator == ' ' && c.isSpace())) {
+            switch (mode) {
+            case sep: if (separator != ' ') tokens << ""; break;
+            case unq: mode = sep; tokens << tok; tok = ""; break;
+            case q1: case q2: tok += c; break;
+            }
 
-	} else if (c == '\\') {
-	    if (++i < s.length()) {
-		c = s[i];
-		switch (mode) {
-		case sep: mode = unq; tok += c; break;
+        } else if (c == '\\') {
+            if (++i < s.length()) {
+                c = s[i];
+                switch (mode) {
+                case sep: mode = unq; tok += c; break;
                 case unq: case q1: case q2: tok += c; break;
-		}
-	    }
+                }
+            }
 
-	} else {
-	    switch (mode) {
-	    case sep: mode = unq; tok += c; break;
+        } else {
+            switch (mode) {
+            case sep: mode = unq; tok += c; break;
             case unq: case q1: case q2: tok += c; break;
-	    }
-	}
+            }
+        }
     }
 
     if (tok != "" || mode != sep) {
--- a/base/StringBits.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/StringBits.h	Mon Sep 17 13:51:14 2018 +0100
@@ -18,8 +18,8 @@
    This file copyright 2000-2010 Chris Cannam.
 */
 
-#ifndef _STRING_BITS_H_
-#define _STRING_BITS_H_
+#ifndef SV_STRING_BITS_H
+#define SV_STRING_BITS_H
 
 #include <QString>
 #include <QStringList>
--- a/base/TempDirectory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/TempDirectory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _TEMP_DIRECTORY_H_
-#define _TEMP_DIRECTORY_H_
+#ifndef SV_TEMP_DIRECTORY_H
+#define SV_TEMP_DIRECTORY_H
 
 #include <QString>
 #include <QMutex>
--- a/base/TempWriteFile.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/TempWriteFile.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -27,8 +27,8 @@
     temp.setAutoRemove(false);
     temp.open(); // creates the file and opens it atomically
     if (temp.error()) {
-	cerr << "TempWriteFile: Failed to create temporary file in directory of " << m_target << ": " << temp.errorString() << endl;
-	throw FileOperationFailed(temp.fileName(), "creation");
+        SVCERR << "TempWriteFile: Failed to create temporary file in directory of " << m_target << ": " << temp.errorString() << endl;
+        throw FileOperationFailed(temp.fileName(), "creation");
     }
     
     m_temp = temp.fileName();
@@ -38,8 +38,8 @@
 TempWriteFile::~TempWriteFile()
 {
     if (m_temp != "") {
-	QDir dir(QFileInfo(m_temp).dir());
-	dir.remove(m_temp);
+        QDir dir(QFileInfo(m_temp).dir());
+        dir.remove(m_temp);
     }
 }
 
@@ -54,14 +54,18 @@
 {
     if (m_temp == "") return;
 
-    QDir dir(QFileInfo(m_temp).dir());
-    // According to  http://doc.trolltech.com/4.4/qdir.html#rename
-    // some systems fail, if renaming over an existing file.
-    // Therefore, delete first the existing file.
-    if (dir.exists(m_target)) dir.remove(m_target);
-    if (!dir.rename(m_temp, m_target)) {
-	cerr << "TempWriteFile: Failed to rename temporary file " << m_temp << " to target " << m_target << endl;
-	throw FileOperationFailed(m_temp, "rename");
+    QFile tempFile(m_temp);
+    QFile targetFile(m_target);
+    
+    if (targetFile.exists()) {
+        if (!targetFile.remove()) {
+            SVCERR << "TempWriteFile: WARNING: Failed to remove existing target file " << m_target << " prior to moving temporary file " << m_temp << " to it" << endl;
+        }
+    }
+    
+    if (!tempFile.rename(m_target)) {
+        SVCERR << "TempWriteFile: Failed to rename temporary file " << m_temp << " to target " << m_target << endl;
+        throw FileOperationFailed(m_temp, "rename");
     }
 
     m_temp = "";
--- a/base/TempWriteFile.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/TempWriteFile.h	Mon Sep 17 13:51:14 2018 +0100
@@ -12,8 +12,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _TEMP_WRITE_FILE_H_
-#define _TEMP_WRITE_FILE_H_
+#ifndef SV_TEMP_WRITE_FILE_H
+#define SV_TEMP_WRITE_FILE_H
 
 #include <QTemporaryFile>
 
@@ -23,7 +23,6 @@
  * use when saving a file over an existing one, to avoid clobbering
  * the original before the save is complete.
  */
-
 class TempWriteFile
 {
 public:
--- a/base/TextMatcher.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/TextMatcher.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -25,7 +25,7 @@
 
 void
 TextMatcher::test(Match &match, QStringList keywords, QString text,
-		  QString textType, int score)
+                  QString textType, int score)
 {
 /*
     if (text.toLower() == keyword.toLower()) {
--- a/base/ViewManagerBase.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ViewManagerBase.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,14 +13,15 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _VIEW_MANAGER_BASE_H_
-#define _VIEW_MANAGER_BASE_H_
+#ifndef SV_VIEW_MANAGER_BASE_H
+#define SV_VIEW_MANAGER_BASE_H
 
 #include <QObject>
 
 #include "Selection.h"
 
 class AudioPlaySource;
+class AudioRecordTarget;
 
 /**
  * Base class for ViewManager, with no GUI content.  This should
@@ -36,6 +37,7 @@
     virtual ~ViewManagerBase();
 
     virtual void setAudioPlaySource(AudioPlaySource *source) = 0;
+    virtual void setAudioRecordTarget(AudioRecordTarget *target) = 0;
 
     virtual sv_frame_t alignPlaybackFrameToReference(sv_frame_t) const = 0;
     virtual sv_frame_t alignReferenceToPlaybackFrame(sv_frame_t) const = 0;
--- a/base/Window.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/Window.h	Mon Sep 17 13:51:14 2018 +0100
@@ -57,11 +57,11 @@
         encache();
     }
     Window &operator=(const Window &w) {
-	if (&w == this) return *this;
-	m_type = w.m_type;
-	m_size = w.m_size;
-	encache();
-	return *this;
+        if (&w == this) return *this;
+        m_type = w.m_type;
+        m_size = w.m_size;
+        encache();
+        return *this;
     }
     virtual ~Window() {
         breakfastquay::deallocate(m_cache);
@@ -107,38 +107,38 @@
     int i;
 
     switch (m_type) {
-		
+                
     case RectangularWindow:
-	for (i = 0; i < n; ++i) {
-	    m_cache[i] *= T(0.5);
-	}
-	break;
-	    
+        for (i = 0; i < n; ++i) {
+            m_cache[i] *= T(0.5);
+        }
+        break;
+            
     case BartlettWindow:
-	for (i = 0; i < n/2; ++i) {
-	    m_cache[i] *= T(i) / T(n/2);
-	    m_cache[i + n/2] *= T(1.0) - T(i) / T(n/2);
-	}
-	break;
-	    
+        for (i = 0; i < n/2; ++i) {
+            m_cache[i] *= T(i) / T(n/2);
+            m_cache[i + n/2] *= T(1.0) - T(i) / T(n/2);
+        }
+        break;
+            
     case HammingWindow:
         cosinewin(m_cache, 0.54, 0.46, 0.0, 0.0);
-	break;
-	    
+        break;
+            
     case HanningWindow:
         cosinewin(m_cache, 0.50, 0.50, 0.0, 0.0);
-	break;
-	    
+        break;
+            
     case BlackmanWindow:
         cosinewin(m_cache, 0.42, 0.50, 0.08, 0.0);
-	break;
-	    
+        break;
+            
     case GaussianWindow:
-	for (i = 0; i < n; ++i) {
+        for (i = 0; i < n; ++i) {
             m_cache[i] *= T(pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2)));
-	}
-	break;
-	    
+        }
+        break;
+            
     case ParzenWindow:
     {
         int N = n-1;
@@ -158,13 +158,13 @@
 
     case NuttallWindow:
         cosinewin(m_cache, 0.3635819, 0.4891775, 0.1365995, 0.0106411);
-	break;
+        break;
 
     case BlackmanHarrisWindow:
         cosinewin(m_cache, 0.35875, 0.48829, 0.14128, 0.01168);
         break;
     }
-	
+        
     m_area = 0;
     for (int i = 0; i < n; ++i) {
         m_area += m_cache[i];
--- a/base/XmlExportable.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/XmlExportable.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -41,11 +41,11 @@
 XmlExportable::encodeEntities(QString s)
 {
     s
-	.replace("&", "&amp;")
-	.replace("<", "&lt;")
-	.replace(">", "&gt;")
-	.replace("\"", "&quot;")
-	.replace("'", "&apos;");
+        .replace("&", "&amp;")
+        .replace("<", "&lt;")
+        .replace(">", "&gt;")
+        .replace("\"", "&quot;")
+        .replace("'", "&apos;");
 
     return s;
 }
@@ -77,7 +77,7 @@
     static int maxId = 0;
     
     if (idMap.find(object) == idMap.end()) {
-	idMap[object] = maxId++;
+        idMap[object] = maxId++;
     }
 
     return idMap[object];
--- a/base/XmlExportable.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/XmlExportable.h	Mon Sep 17 13:51:14 2018 +0100
@@ -40,7 +40,7 @@
      * Do not override this unless you really know what you're doing.
      */
     virtual QString toXmlString(QString indent = "",
-				QString extraAttributes = "") const;
+                                QString extraAttributes = "") const;
 
     static QString encodeEntities(QString);
 
--- a/base/ZoomConstraint.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/ZoomConstraint.h	Mon Sep 17 13:51:14 2018 +0100
@@ -35,9 +35,9 @@
     virtual ~ZoomConstraint() { }
 
     enum RoundingDirection {
-	RoundDown,
-	RoundUp,
-	RoundNearest
+        RoundDown,
+        RoundUp,
+        RoundNearest
     };
 
     /**
@@ -52,7 +52,7 @@
      */
     virtual ZoomLevel getNearestZoomLevel(ZoomLevel requestedZoomLevel,
                                           RoundingDirection = RoundNearest)
-	const
+        const
     {
         if (getMaxZoomLevel() < requestedZoomLevel) return getMaxZoomLevel();
 	else return requestedZoomLevel;
--- a/base/test/TestColumnOp.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestColumnOp.h	Mon Sep 17 13:51:14 2018 +0100
@@ -55,7 +55,7 @@
         QCOMPARE(C::applyGain({}, 1.0), Column());
         Column c { 1, 2, 3, -4, 5, 6 };
         Column actual(C::applyGain(c, 1.5));
-        Column expected { 1.5, 3, 4.5, -6, 7.5, 9 };
+        Column expected { 1.5f, 3, 4.5f, -6, 7.5f, 9 };
         QCOMPARE(actual, expected);
         actual = C::applyGain(c, 1.0);
         QCOMPARE(actual, c);
@@ -68,7 +68,7 @@
         QCOMPARE(C::fftScale({}, 2.0), Column());
         Column c { 1, 2, 3, -4, 5 };
         Column actual(C::fftScale(c, 8));
-        Column expected { 0.25, 0.5, 0.75, -1, 1.25 };
+        Column expected { 0.25f, 0.5f, 0.75f, -1, 1.25f };
         QCOMPARE(actual, expected);
     }
 
@@ -79,33 +79,33 @@
     }
 
     void isPeak_obvious() {
-        Column c { 0.4, 0.5, 0.3 };
+        Column c { 0.4f, 0.5f, 0.3f };
         QVERIFY(!C::isPeak(c, 0));
         QVERIFY(C::isPeak(c, 1));
         QVERIFY(!C::isPeak(c, 2));
     }
 
     void isPeak_edges() {
-        Column c { 0.5, 0.4, 0.3 };
+        Column c { 0.5f, 0.4f, 0.3f };
         QVERIFY(C::isPeak(c, 0));
         QVERIFY(!C::isPeak(c, 1));
         QVERIFY(!C::isPeak(c, 2));
         QVERIFY(!C::isPeak(c, 3));
         QVERIFY(!C::isPeak(c, -1));
-        c = { 1.4, 1.5 };
+        c = { 1.4f, 1.5f };
         QVERIFY(!C::isPeak(c, 0));
         QVERIFY(C::isPeak(c, 1));
     }
 
     void isPeak_flat() {
-        Column c { 0.0, 0.0, 0.0 };
+        Column c { 0.0f, 0.0f, 0.0f };
         QVERIFY(C::isPeak(c, 0));
         QVERIFY(!C::isPeak(c, 1));
         QVERIFY(!C::isPeak(c, 2));
     }
 
     void isPeak_mixedSign() {
-        Column c { 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 };
+        Column c { 0.4f, -0.5f, -0.3f, -0.6f, 0.1f, -0.3f };
         QVERIFY(C::isPeak(c, 0));
         QVERIFY(!C::isPeak(c, 1));
         QVERIFY(C::isPeak(c, 2));
@@ -115,12 +115,12 @@
     }
 
     void isPeak_duplicate() {
-        Column c({ 0.5, 0.5, 0.4, 0.4 });
+        Column c({ 0.5f, 0.5f, 0.4f, 0.4f });
         QVERIFY(C::isPeak(c, 0));
         QVERIFY(!C::isPeak(c, 1));
         QVERIFY(!C::isPeak(c, 2));
         QVERIFY(!C::isPeak(c, 3));
-        c = { 0.4, 0.4, 0.5, 0.5 };
+        c = { 0.4f, 0.4f, 0.5f, 0.5f };
         QVERIFY(C::isPeak(c, 0)); // counterintuitive but necessary
         QVERIFY(!C::isPeak(c, 1));
         QVERIFY(C::isPeak(c, 2));
@@ -129,16 +129,17 @@
 
     void peakPick() {
         QCOMPARE(C::peakPick({}), Column());
-        Column c({ 0.5, 0.5, 0.4, 0.4 });
-        QCOMPARE(C::peakPick(c), Column({ 0.5, 0.0, 0.0, 0.0 }));
-        c = Column({ 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 });
-        QCOMPARE(C::peakPick(c), Column({ 0.4, 0.0, -0.3, 0.0, 0.1, 0.0 }));
+        Column c({ 0.5f, 0.5f, 0.4f, 0.4f });
+        QCOMPARE(C::peakPick(c), Column({ 0.5f, 0.0f, 0.0f, 0.0f }));
+        c = Column({ 0.4f, -0.5f, -0.3f, -0.6f, 0.1f, -0.3f });
+        QCOMPARE(C::peakPick(c), Column({ 0.4f, 0.0f, -0.3f, 0.0f, 0.1f, 0.0f }));
     }
 
     void normalize_null() {
         QCOMPARE(C::normalize({}, ColumnNormalization::None), Column());
         QCOMPARE(C::normalize({}, ColumnNormalization::Sum1), Column());
         QCOMPARE(C::normalize({}, ColumnNormalization::Max1), Column());
+        QCOMPARE(C::normalize({}, ColumnNormalization::Range01), Column());
         QCOMPARE(C::normalize({}, ColumnNormalization::Hybrid), Column());
     }
 
@@ -155,44 +156,56 @@
     void normalize_sum1() {
         Column c { 1, 2, 4, 3 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Sum1),
-                 Column({ 0.1, 0.2, 0.4, 0.3 }));
+                 Column({ 0.1f, 0.2f, 0.4f, 0.3f }));
     }
 
     void normalize_sum1_mixedSign() {
         Column c { 1, 2, -4, -3 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Sum1),
-                 Column({ 0.1, 0.2, -0.4, -0.3 }));
+                 Column({ 0.1f, 0.2f, -0.4f, -0.3f }));
     }
 
     void normalize_max1() {
         Column c { 4, 3, 2, 1 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Max1),
-                 Column({ 1.0, 0.75, 0.5, 0.25 }));
+                 Column({ 1.0f, 0.75f, 0.5f, 0.25f }));
     }
 
     void normalize_max1_mixedSign() {
         Column c { -4, -3, 2, 1 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Max1),
-                 Column({ -1.0, -0.75, 0.5, 0.25 }));
+                 Column({ -1.0f, -0.75f, 0.5f, 0.25f }));
+    }
+
+    void normalize_range01() {
+        Column c { 4, 3, 2, 1 };
+        QCOMPARE(C::normalize(c, ColumnNormalization::Range01),
+                 Column({ 1.0f, 2.f/3.f, 1.f/3.f, 0.0f }));
+    }
+
+    void normalize_range01_mixedSign() {
+        Column c { -2, -3, 2, 1 };
+        QCOMPARE(C::normalize(c, ColumnNormalization::Range01),
+                 Column({ 0.2f, 0.0f, 1.0f, 0.8f }));
     }
 
     void normalize_hybrid() {
         // with max == 99, log10(max+1) == 2 so scale factor will be 2/99
         Column c { 22, 44, 99, 66 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid),
-                 Column({ 44.0/99.0, 88.0/99.0, 2.0, 132.0/99.0 }));
+                 Column({ 44.0f/99.0f, 88.0f/99.0f, 2.0f, 132.0f/99.0f }));
     }
 
     void normalize_hybrid_mixedSign() {
         // with max == 99, log10(max+1) == 2 so scale factor will be 2/99
         Column c { 22, 44, -99, -66 };
         QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid),
-                 Column({ 44.0/99.0, 88.0/99.0, -2.0, -132.0/99.0 }));
+                 Column({ 44.0f/99.0f, 88.0f/99.0f, -2.0f, -132.0f/99.0f }));
     }
     
     void distribute_simple() {
         Column in { 1, 2, 3 };
-        BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+        BinMapping binfory { 0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f };
         Column expected { 1, 1, 2, 2, 3, 3 };
         Column actual(C::distribute(in, 6, binfory, 0, false));
         report(actual);
@@ -201,7 +214,7 @@
     
     void distribute_simple_interpolated() {
         Column in { 1, 2, 3 };
-        BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+        BinMapping binfory { 0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f };
         // There is a 0.5-bin offset from the distribution you might
         // expect, because this corresponds visually to the way that
         // bin values are duplicated upwards in simple_distribution.
@@ -209,7 +222,7 @@
         // non-interpolated views retains the visual position of each
         // bin peak as somewhere in the middle of the scale area for
         // that bin.
-        Column expected { 1, 1, 1.5, 2, 2.5, 3 };
+        Column expected { 1, 1, 1.5f, 2, 2.5f, 3 };
         Column actual(C::distribute(in, 6, binfory, 0, true));
         report(actual);
         QCOMPARE(actual, expected);
@@ -217,7 +230,7 @@
     
     void distribute_nonlinear() {
         Column in { 1, 2, 3 };
-        BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 };
+        BinMapping binfory { 0.0f, 0.2f, 0.5f, 1.0f, 2.0f, 2.5f };
         Column expected { 1, 1, 1, 2, 3, 3 };
         Column actual(C::distribute(in, 6, binfory, 0, false));
         report(actual);
@@ -227,7 +240,7 @@
     void distribute_nonlinear_interpolated() {
         // See distribute_simple_interpolated
         Column in { 1, 2, 3 };
-        BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 };
+        BinMapping binfory { 0.0f, 0.2f, 0.5f, 1.0f, 2.0f, 2.5f };
         Column expected { 1, 1, 1, 1.5, 2.5, 3 };
         Column actual(C::distribute(in, 6, binfory, 0, true));
         report(actual);
@@ -236,7 +249,7 @@
     
     void distribute_shrinking() {
         Column in { 4, 1, 2, 3, 5, 6 };
-        BinMapping binfory { 0.0, 2.0, 4.0 };
+        BinMapping binfory { 0.0f, 2.0f, 4.0f };
         Column expected { 4, 3, 6 };
         Column actual(C::distribute(in, 3, binfory, 0, false));
         report(actual);
@@ -247,7 +260,7 @@
         // should be same as distribute_shrinking, we don't
         // interpolate when resizing down
         Column in { 4, 1, 2, 3, 5, 6 };
-        BinMapping binfory { 0.0, 2.0, 4.0 };
+        BinMapping binfory { 0.0f, 2.0f, 4.0f };
         Column expected { 4, 3, 6 };
         Column actual(C::distribute(in, 3, binfory, 0, true));
         report(actual);
@@ -259,13 +272,13 @@
         // shrinking some bins but expanding others.  See
         // distribute_simple_interpolated for note on 0.5 offset
         Column in { 4, 1, 2, 3, 5, 6 };
-        BinMapping binfory { 0.0, 3.0, 4.0, 4.5 };
-        Column expected { 4.0, 2.5, 4.0, 5.0 };
+        BinMapping binfory { 0.0f, 3.0f, 4.0f, 4.5f };
+        Column expected { 4.0f, 2.5f, 4.0f, 5.0f };
         Column actual(C::distribute(in, 4, binfory, 0, true));
         report(actual);
         QCOMPARE(actual, expected);
-        binfory = BinMapping { 0.5, 1.0, 2.0, 5.0 };
-        expected = { 4.0, 2.5, 1.5, 5.5 };
+        binfory = BinMapping { 0.5f, 1.0f, 2.0f, 5.0f };
+        expected = { 4.0f, 2.5f, 1.5f, 5.5f };
         actual = (C::distribute(in, 4, binfory, 0, true));
         report(actual);
         QCOMPARE(actual, expected);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestLogRange.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,333 @@
+/* -*- 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_LOG_RANGE_H
+#define TEST_LOG_RANGE_H
+
+#include "../LogRange.h"
+
+#include <QObject>
+#include <QtTest>
+
+#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+class TestLogRange : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+    void mapPositiveAboveDefaultThreshold()
+    {
+        QCOMPARE(LogRange::map(10.0), 1.0);
+        QCOMPARE(LogRange::map(100.0), 2.0);
+        QCOMPARE(LogRange::map(0.1), -1.0);
+        QCOMPARE(LogRange::map(1.0), 0.0);
+        QCOMPARE(LogRange::map(0.0000001), -7.0);
+        QCOMPARE(LogRange::map(20.0), log10(20.0));
+    }
+    
+    void mapPositiveAboveSetThreshold()
+    {
+        QCOMPARE(LogRange::map(10.0, -10.0), 1.0);
+        QCOMPARE(LogRange::map(100.0, 1.0), 2.0);
+        QCOMPARE(LogRange::map(0.1, -5.0), -1.0);
+        QCOMPARE(LogRange::map(1.0, -0.01), 0.0);
+        QCOMPARE(LogRange::map(0.0000001, -20.0), -7.0);
+        QCOMPARE(LogRange::map(20.0, 0.0), log10(20.0));
+    }
+
+    void mapZeroDefaultThreshold()
+    {
+        QCOMPARE(LogRange::map(0.0), -10.0);
+    }
+
+    void mapZeroSetThreshold()
+    {
+        QCOMPARE(LogRange::map(0.0, 12.0), 12.0);
+        QCOMPARE(LogRange::map(0.0, -12.0), -12.0);
+        QCOMPARE(LogRange::map(0.0, 0.0), 0.0);
+    }
+    
+    void mapPositiveBelowDefaultThreshold()
+    {
+        // The threshold is used only for zero values, not for very
+        // small ones -- it's arguably a stand-in or replacement value
+        // rather than a threshold. So this should behave the same as
+        // for values above the threshold.
+        QCOMPARE(LogRange::map(1e-10), -10.0);
+        QCOMPARE(LogRange::map(1e-20), -20.0);
+        QCOMPARE(LogRange::map(1e-100), -100.0);
+    }
+
+    void mapPositiveBelowSetThreshold()
+    {
+        // As above
+        QCOMPARE(LogRange::map(10.0, 4.0), 1.0);
+        QCOMPARE(LogRange::map(1e-10, 4.0), -10.0);
+        QCOMPARE(LogRange::map(1e-20, -15.0), -20.0);
+        QCOMPARE(LogRange::map(1e-100, -100.0), -100.0);
+    }
+
+    void mapNegative()
+    {
+        // Should always return map of absolute value. These are
+        // picked from vaarious of the above tests.
+        
+        QCOMPARE(LogRange::map(-10.0), 1.0);
+        QCOMPARE(LogRange::map(-100.0), 2.0);
+        QCOMPARE(LogRange::map(-0.1), -1.0);
+        QCOMPARE(LogRange::map(-1.0), 0.0);
+        QCOMPARE(LogRange::map(-0.0000001), -7.0);
+        QCOMPARE(LogRange::map(-20.0), log10(20.0));
+        QCOMPARE(LogRange::map(-10.0, 4.0), 1.0);
+        QCOMPARE(LogRange::map(-1e-10, 4.0), -10.0);
+        QCOMPARE(LogRange::map(-1e-20, -15.0), -20.0);
+        QCOMPARE(LogRange::map(-1e-100, -100.0), -100.0);
+        QCOMPARE(LogRange::map(-0.0, 12.0), 12.0);
+        QCOMPARE(LogRange::map(-0.0, -12.0), -12.0);
+        QCOMPARE(LogRange::map(-0.0, 0.0), 0.0);
+    }
+
+    void unmap()
+    {
+        // Simply pow(10, x)
+
+        QCOMPARE(LogRange::unmap(0.0), 1.0);
+        QCOMPARE(LogRange::unmap(1.0), 10.0);
+        QCOMPARE(LogRange::unmap(-1.0), 0.1);
+        QCOMPARE(LogRange::unmap(100.0), 1e+100);
+        QCOMPARE(LogRange::unmap(-100.0), 1e-100);
+    }
+
+    void mapRangeAllPositiveDefaultThreshold()
+    {
+        double min, max;
+
+        min = 1.0; max = 10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        min = 10.0; max = 1.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = 10.0; max = 10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, 1.0); QCOMPARE(max, log10(11.0));
+    }
+
+    void mapRangeAllPositiveSetThreshold()
+    {
+        double min, max;
+
+        min = 1.0; max = 10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        min = 10.0; max = 1.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = 10.0; max = 10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, 1.0); QCOMPARE(max, log10(11.0));
+    }
+
+    void mapRangeAllNegativeDefaultThreshold()
+    {
+        double min, max;
+
+        min = -1.0; max = -10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        min = -10.0; max = -1.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = -10.0; max = -10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, log10(9.0)); QCOMPARE(max, 1.0);
+    }
+    
+    void mapRangeAllNegativeSetThreshold()
+    {
+        double min, max;
+
+        min = -1.0; max = -10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        min = -10.0; max = -1.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, 0.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = -10.0; max = -10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, log10(9.0)); QCOMPARE(max, 1.0);
+    }
+    
+    void mapRangeAllNonNegativeDefaultThreshold()
+    {
+        double min, max;
+
+        min = 0.0; max = 10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        min = 10.0; max = 0.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = 0.0; max = 0.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 0.0);
+    }
+    
+    void mapRangeAllNonNegativeSetThreshold()
+    {
+        double min, max;
+
+        min = 0.0; max = 10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        min = 10.0; max = 0.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        // if equal, the function uses an arbitrary 1.0 range before mapping
+        min = 0.0; max = 0.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 0.0);
+    }
+    
+    void mapRangeAllNonPositiveDefaultThreshold()
+    {
+        double min, max;
+
+        min = 0.0; max = -10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        min = -10.0; max = 0.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+    }
+    
+    void mapRangeAllNonPositiveSetThreshold()
+    {
+        double min, max;
+
+        min = 0.0; max = -10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        min = -10.0; max = 0.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+    }
+    
+    void mapRangeSpanningZeroDefaultThreshold()
+    {
+        double min, max;
+
+        min = -1.0; max = 10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        min = -100.0; max = 1.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 2.0);
+
+        min = -10.0; max = 1e-200;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        min = 1e-200; max = -10.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        min = -1e-200; max = 100.0;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 2.0);
+
+        min = 10.0; max = -1e-200;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -10.0); QCOMPARE(max, 1.0);
+
+        // if none of the input range is above the threshold in
+        // magnitude, but it still spans zero, we use the input max as
+        // threshold and then add 1 for range
+        min = -1e-200; max = 1e-300;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -201.0); QCOMPARE(max, -200.0);
+
+        min = 1e-200; max = -1e-300;
+        LogRange::mapRange(min, max);
+        QCOMPARE(min, -201.0); QCOMPARE(max, -200.0);
+    }
+    
+    void mapRangeSpanningZeroSetThreshold()
+    {
+        double min, max;
+
+        min = -1.0; max = 10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        min = -100.0; max = 1.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 2.0);
+
+        min = -10.0; max = 1e-200;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        min = 1e-200; max = -10.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        min = -1e-200; max = 100.0;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 2.0);
+
+        min = 10.0; max = -1e-200;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -4.0); QCOMPARE(max, 1.0);
+
+        // if none of the input range is above the threshold in
+        // magnitude, but it still spans zero, we use the input max as
+        // threshold and then add 1 for range
+        min = -1e-200; max = 1e-300;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -201.0); QCOMPARE(max, -200.0);
+
+        min = 1e-200; max = -1e-300;
+        LogRange::mapRange(min, max, -4.0);
+        QCOMPARE(min, -201.0); QCOMPARE(max, -200.0);
+    }
+    
+};
+
+#endif
--- a/base/test/TestOurRealTime.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestOurRealTime.h	Mon Sep 17 13:51:14 2018 +0100
@@ -42,242 +42,247 @@
 
     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);
+        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));
+        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));
+        // 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(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(1, -ONE_BILLION-ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
         QCOMPARE(RealTime(-1, ONE_BILLION+ONE_BILLION/2), RealTime(0, 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(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), RealTime(0, 0));
+        QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -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);
+        QCOMPARE(RealTime(1, -ONE_BILLION/2).sec, 0);
+        QCOMPARE(RealTime(1, -ONE_BILLION/2).nsec, ONE_BILLION/2);
+        QCOMPARE(RealTime(-1, ONE_BILLION/2).sec, 0);
+        QCOMPARE(RealTime(-1, ONE_BILLION/2).nsec, -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), 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(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));
+        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));
 
-    	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;
+        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 = 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));
+        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));
+        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, 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);
+        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);
+        RealTime r;
+        r = RealTime(0, 0);
+        QCOMPARE(r, RealTime::zeroTime);
+        r = RealTime(0, ONE_BILLION/2);
         QCOMPARE(r.sec, 0);
         QCOMPARE(r.nsec, ONE_BILLION/2);
-	r = RealTime(-1, -ONE_BILLION/2);
+        r = RealTime(-1, -ONE_BILLION/2);
         QCOMPARE(r.sec, -1);
         QCOMPARE(r.nsec, -ONE_BILLION/2);
     }
 
     void plus()
     {
-	QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0));
+        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(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(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));
+        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, 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(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(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));
+        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));
+        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) {
+        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);
+                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(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);
+                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;
-	    }
-	}
+                prev = curr;
+            }
+        }
     }
 
     void frame()
--- a/base/test/TestPitch.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestPitch.h	Mon Sep 17 13:51:14 2018 +0100
@@ -32,76 +32,76 @@
 
 private slots:
     void init() {
-	Preferences::getInstance()->setOctaveOfMiddleC(4);
-	Preferences::getInstance()->setTuningFrequency(440);
+        Preferences::getInstance()->setOctaveOfMiddleC(4);
+        Preferences::getInstance()->setTuningFrequency(440);
     }
 
     void pitchLabel()
     {
-	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C4"));
-	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A4"));
-	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#4"));
-	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db4"));
-	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B3"));
-	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B3"));
-	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-1"));
+        QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C4"));
+        QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A4"));
+        QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#4"));
+        QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db4"));
+        QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B3"));
+        QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B3"));
+        QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-1"));
 
-	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C4-40c"));
-	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C4+40c"));
-	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#3+4c"));
+        QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C4-40c"));
+        QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C4+40c"));
+        QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#3+4c"));
 
-	Preferences::getInstance()->setOctaveOfMiddleC(3);
+        Preferences::getInstance()->setOctaveOfMiddleC(3);
 
-	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C3"));
-	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A3"));
-	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#3"));
-	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db3"));
-	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B2"));
-	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B2"));
-	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-2"));
+        QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C3"));
+        QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A3"));
+        QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#3"));
+        QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db3"));
+        QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B2"));
+        QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B2"));
+        QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-2"));
 
-	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C3-40c"));
-	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C3+40c"));
-	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#2+4c"));
+        QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C3-40c"));
+        QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C3+40c"));
+        QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#2+4c"));
     }
 
     void pitchLabelForFrequency()
     {
-	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 440, false), QString("A4"));
-	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 220, false), QString("A5"));
-	QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
+        QCOMPARE(Pitch::getPitchLabelForFrequency(440, 440, false), QString("A4"));
+        QCOMPARE(Pitch::getPitchLabelForFrequency(440, 220, false), QString("A5"));
+        QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
     }
 
 #define MIDDLE_C 261.6255653005986
 
     void frequencyForPitch()
     {
-	QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.0);
-	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.0);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.0);
+        QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
+        QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.0);
+        QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.0);
+        QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.0);
     }
 
     void pitchForFrequency()
     {
-	double centsOffset = 0.0;
-	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
-	QCOMPARE(centsOffset, 0.0);
-	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
-	QCOMPARE(int(centsOffset), -4);
-	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
-	QCOMPARE(centsOffset, 0.0);
+        double centsOffset = 0.0;
+        QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+        QCOMPARE(centsOffset + 1.0, 1.0); // avoid ineffective fuzzy-compare to 0
+        QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+        QCOMPARE(int(centsOffset), -4);
+        QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+        QCOMPARE(centsOffset + 1.0, 1.0);
     }
 
     void pitchForFrequencyF()
     {
-	float centsOffset = 0.f;
-	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
-	QCOMPARE(centsOffset, 0.f);
-	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
-	QCOMPARE(int(centsOffset), -4);
-	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
-	QCOMPARE(centsOffset, 0.f);
+        float centsOffset = 0.f;
+        QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+        QCOMPARE(centsOffset + 1.f, 1.f); // avoid ineffective fuzzy-compare to 0
+        QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+        QCOMPARE(int(centsOffset), -4);
+        QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+        QCOMPARE(centsOffset + 1.f, 1.f);
     }
 };
 
--- a/base/test/TestRangeMapper.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestRangeMapper.h	Mon Sep 17 13:51:14 2018 +0100
@@ -32,250 +32,250 @@
 private slots:
     void linearUpForward()
     {
-	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getPositionForValue(0.5), 1);
-	QCOMPARE(rm.getPositionForValue(4.0), 8);
-	QCOMPARE(rm.getPositionForValue(3.0), 6);
-	QCOMPARE(rm.getPositionForValue(3.1), 6);
-	QCOMPARE(rm.getPositionForValue(3.4), 7);
-	QCOMPARE(rm.getPositionForValue(0.2), 1);
-	QCOMPARE(rm.getPositionForValue(-12), 1);
-	QCOMPARE(rm.getPositionForValue(6.1), 8);
-	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6);
-	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0);
-	QCOMPARE(rm.getPositionForValueUnclamped(-12), -24);
-	QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12);
+        LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getPositionForValue(0.5), 1);
+        QCOMPARE(rm.getPositionForValue(4.0), 8);
+        QCOMPARE(rm.getPositionForValue(3.0), 6);
+        QCOMPARE(rm.getPositionForValue(3.1), 6);
+        QCOMPARE(rm.getPositionForValue(3.4), 7);
+        QCOMPARE(rm.getPositionForValue(0.2), 1);
+        QCOMPARE(rm.getPositionForValue(-12), 1);
+        QCOMPARE(rm.getPositionForValue(6.1), 8);
+        QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6);
+        QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0);
+        QCOMPARE(rm.getPositionForValueUnclamped(-12), -24);
+        QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12);
     }
 
     void linearDownForward()
     {
-	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getPositionForValue(0.5), 8);
-	QCOMPARE(rm.getPositionForValue(4.0), 1);
-	QCOMPARE(rm.getPositionForValue(3.0), 3);
-	QCOMPARE(rm.getPositionForValue(3.1), 3);
-	QCOMPARE(rm.getPositionForValue(3.4), 2);
-	QCOMPARE(rm.getPositionForValue(0.2), 8);
-	QCOMPARE(rm.getPositionForValue(-12), 8);
-	QCOMPARE(rm.getPositionForValue(6.1), 1);
-	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3);
-	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9);
-	QCOMPARE(rm.getPositionForValueUnclamped(-12), 33);
-	QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3);
+        LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getPositionForValue(0.5), 8);
+        QCOMPARE(rm.getPositionForValue(4.0), 1);
+        QCOMPARE(rm.getPositionForValue(3.0), 3);
+        QCOMPARE(rm.getPositionForValue(3.1), 3);
+        QCOMPARE(rm.getPositionForValue(3.4), 2);
+        QCOMPARE(rm.getPositionForValue(0.2), 8);
+        QCOMPARE(rm.getPositionForValue(-12), 8);
+        QCOMPARE(rm.getPositionForValue(6.1), 1);
+        QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3);
+        QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9);
+        QCOMPARE(rm.getPositionForValueUnclamped(-12), 33);
+        QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3);
     }
 
     void linearUpBackward()
     {
-	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getValueForPosition(1), 0.5);
-	QCOMPARE(rm.getValueForPosition(8), 4.0);
-	QCOMPARE(rm.getValueForPosition(6), 3.0);
-	QCOMPARE(rm.getValueForPosition(7), 3.5);
-	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
-	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
-	QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0);
+        LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getValueForPosition(1), 0.5);
+        QCOMPARE(rm.getValueForPosition(8), 4.0);
+        QCOMPARE(rm.getValueForPosition(6), 3.0);
+        QCOMPARE(rm.getValueForPosition(7), 3.5);
+        QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
+        QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+        QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(0) + 1.0, 0.0 + 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0);
     }
 
     void linearDownBackward()
     {
-	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getValueForPosition(8), 0.5);
-	QCOMPARE(rm.getValueForPosition(1), 4.0);
-	QCOMPARE(rm.getValueForPosition(3), 3.0);
-	QCOMPARE(rm.getValueForPosition(2), 3.5);
-	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
-	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
-	QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0);
-	QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0);
+        LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getValueForPosition(8), 0.5);
+        QCOMPARE(rm.getValueForPosition(1), 4.0);
+        QCOMPARE(rm.getValueForPosition(3), 3.0);
+        QCOMPARE(rm.getValueForPosition(2), 3.5);
+        QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
+        QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+        QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(9) + 1.0, 0.0 + 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0);
     }
 
     void logUpForward()
     {
-	LogRangeMapper rm(3, 7, 10, 100000, "x", false);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getPositionForValue(10.0), 3);
-	QCOMPARE(rm.getPositionForValue(100000.0), 7);
-	QCOMPARE(rm.getPositionForValue(1.0), 3);
-	QCOMPARE(rm.getPositionForValue(1000000.0), 7);
-	QCOMPARE(rm.getPositionForValue(1000.0), 5);
-	QCOMPARE(rm.getPositionForValue(900.0), 5);
-	QCOMPARE(rm.getPositionForValue(20000), 6);
-	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2);
-	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8);
-	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
+        LogRangeMapper rm(3, 7, 10, 100000, "x", false);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getPositionForValue(10.0), 3);
+        QCOMPARE(rm.getPositionForValue(100000.0), 7);
+        QCOMPARE(rm.getPositionForValue(1.0), 3);
+        QCOMPARE(rm.getPositionForValue(1000000.0), 7);
+        QCOMPARE(rm.getPositionForValue(1000.0), 5);
+        QCOMPARE(rm.getPositionForValue(900.0), 5);
+        QCOMPARE(rm.getPositionForValue(20000), 6);
+        QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2);
+        QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8);
+        QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
     }
 
     void logDownForward()
     {
-	LogRangeMapper rm(3, 7, 10, 100000, "x", true);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getPositionForValue(10.0), 7);
-	QCOMPARE(rm.getPositionForValue(100000.0), 3);
-	QCOMPARE(rm.getPositionForValue(1.0), 7);
-	QCOMPARE(rm.getPositionForValue(1000000.0), 3);
-	QCOMPARE(rm.getPositionForValue(1000.0), 5);
-	QCOMPARE(rm.getPositionForValue(900.0), 5);
-	QCOMPARE(rm.getPositionForValue(20000), 4);
-	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8);
-	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2);
-	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
+        LogRangeMapper rm(3, 7, 10, 100000, "x", true);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getPositionForValue(10.0), 7);
+        QCOMPARE(rm.getPositionForValue(100000.0), 3);
+        QCOMPARE(rm.getPositionForValue(1.0), 7);
+        QCOMPARE(rm.getPositionForValue(1000000.0), 3);
+        QCOMPARE(rm.getPositionForValue(1000.0), 5);
+        QCOMPARE(rm.getPositionForValue(900.0), 5);
+        QCOMPARE(rm.getPositionForValue(20000), 4);
+        QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8);
+        QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2);
+        QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
     }
 
     void logUpBackward()
     {
-	LogRangeMapper rm(3, 7, 10, 100000, "x", false);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getValueForPosition(3), 10.0);
-	QCOMPARE(rm.getValueForPosition(7), 100000.0);
-	QCOMPARE(rm.getValueForPosition(5), 1000.0);
-	QCOMPARE(rm.getValueForPosition(6), 10000.0);
-	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
-	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
-	QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0);
+        LogRangeMapper rm(3, 7, 10, 100000, "x", false);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getValueForPosition(3), 10.0);
+        QCOMPARE(rm.getValueForPosition(7), 100000.0);
+        QCOMPARE(rm.getValueForPosition(5), 1000.0);
+        QCOMPARE(rm.getValueForPosition(6), 10000.0);
+        QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
+        QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+        QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0);
         QCOMPARE(rm.getValueForPositionUnclamped(8), 1000000.0);
         QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
     }
 
     void logDownBackward()
     {
-	LogRangeMapper rm(3, 7, 10, 100000, "x", true);
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getValueForPosition(7), 10.0);
-	QCOMPARE(rm.getValueForPosition(3), 100000.0);
-	QCOMPARE(rm.getValueForPosition(5), 1000.0);
-	QCOMPARE(rm.getValueForPosition(4), 10000.0);
-	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
-	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
-	QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0);
+        LogRangeMapper rm(3, 7, 10, 100000, "x", true);
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getValueForPosition(7), 10.0);
+        QCOMPARE(rm.getValueForPosition(3), 100000.0);
+        QCOMPARE(rm.getValueForPosition(5), 1000.0);
+        QCOMPARE(rm.getValueForPosition(4), 10000.0);
+        QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
+        QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+        QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0);
         QCOMPARE(rm.getValueForPositionUnclamped(2), 1000000.0);
         QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
     }
 
     void interpolatingForward()
     {
-	InterpolatingRangeMapper::CoordMap mappings;
-	mappings[1] = 10;
-	mappings[3] = 30;
-	mappings[5] = 70;
-	InterpolatingRangeMapper rm(mappings, "x");
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getPositionForValue(1.0), 10);
-	QCOMPARE(rm.getPositionForValue(0.0), 10);
-	QCOMPARE(rm.getPositionForValue(5.0), 70);
-	QCOMPARE(rm.getPositionForValue(6.0), 70);
-	QCOMPARE(rm.getPositionForValue(3.0), 30);
-	QCOMPARE(rm.getPositionForValue(2.5), 25);
-	QCOMPARE(rm.getPositionForValue(4.5), 60);
-	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
-	QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25);
-	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
+        InterpolatingRangeMapper::CoordMap mappings;
+        mappings[1] = 10;
+        mappings[3] = 30;
+        mappings[5] = 70;
+        InterpolatingRangeMapper rm(mappings, "x");
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getPositionForValue(1.0), 10);
+        QCOMPARE(rm.getPositionForValue(0.0), 10);
+        QCOMPARE(rm.getPositionForValue(5.0), 70);
+        QCOMPARE(rm.getPositionForValue(6.0), 70);
+        QCOMPARE(rm.getPositionForValue(3.0), 30);
+        QCOMPARE(rm.getPositionForValue(2.5), 25);
+        QCOMPARE(rm.getPositionForValue(4.5), 60);
+        QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+        QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25);
+        QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
     }
 
     void interpolatingBackward()
     {
-	InterpolatingRangeMapper::CoordMap mappings;
-	mappings[1] = 10;
-	mappings[3] = 30;
-	mappings[5] = 70;
-	InterpolatingRangeMapper rm(mappings, "x");
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getValueForPosition(10), 1.0);
-	QCOMPARE(rm.getValueForPosition(9), 1.0);
-	QCOMPARE(rm.getValueForPosition(70), 5.0);
-	QCOMPARE(rm.getValueForPosition(80), 5.0);
-	QCOMPARE(rm.getValueForPosition(30), 3.0);
-	QCOMPARE(rm.getValueForPosition(25), 2.5);
-	QCOMPARE(rm.getValueForPosition(60), 4.5);
+        InterpolatingRangeMapper::CoordMap mappings;
+        mappings[1] = 10;
+        mappings[3] = 30;
+        mappings[5] = 70;
+        InterpolatingRangeMapper rm(mappings, "x");
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getValueForPosition(10), 1.0);
+        QCOMPARE(rm.getValueForPosition(9), 1.0);
+        QCOMPARE(rm.getValueForPosition(70), 5.0);
+        QCOMPARE(rm.getValueForPosition(80), 5.0);
+        QCOMPARE(rm.getValueForPosition(30), 3.0);
+        QCOMPARE(rm.getValueForPosition(25), 2.5);
+        QCOMPARE(rm.getValueForPosition(60), 4.5);
     }
 
     void autoLinearForward()
     {
-	AutoRangeMapper::CoordMap mappings;
-	mappings[0.5] = 1;
-	mappings[4.0] = 8;
-	AutoRangeMapper rm1(mappings, "x");
-	QCOMPARE(rm1.getUnit(), QString("x"));
-	QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine);
-	QCOMPARE(rm1.getPositionForValue(0.1), 1);
-	QCOMPARE(rm1.getPositionForValue(0.5), 1);
-	QCOMPARE(rm1.getPositionForValue(4.0), 8);
-	QCOMPARE(rm1.getPositionForValue(4.5), 8);
-	QCOMPARE(rm1.getPositionForValue(3.0), 6);
-	QCOMPARE(rm1.getPositionForValue(3.1), 6);
-	QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0);
-	QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6);
-	QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9);
-	mappings[3.0] = 6;
-	mappings[3.5] = 7;
-	AutoRangeMapper rm2(mappings, "x");
-	QCOMPARE(rm2.getUnit(), QString("x"));
-	QCOMPARE(rm2.getType(), AutoRangeMapper::StraightLine);
-	QCOMPARE(rm2.getPositionForValue(0.5), 1);
-	QCOMPARE(rm2.getPositionForValue(4.0), 8);
-	QCOMPARE(rm2.getPositionForValue(3.0), 6);
-	QCOMPARE(rm2.getPositionForValue(3.1), 6);
+        AutoRangeMapper::CoordMap mappings;
+        mappings[0.5] = 1;
+        mappings[4.0] = 8;
+        AutoRangeMapper rm1(mappings, "x");
+        QCOMPARE(rm1.getUnit(), QString("x"));
+        QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine);
+        QCOMPARE(rm1.getPositionForValue(0.1), 1);
+        QCOMPARE(rm1.getPositionForValue(0.5), 1);
+        QCOMPARE(rm1.getPositionForValue(4.0), 8);
+        QCOMPARE(rm1.getPositionForValue(4.5), 8);
+        QCOMPARE(rm1.getPositionForValue(3.0), 6);
+        QCOMPARE(rm1.getPositionForValue(3.1), 6);
+        QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0);
+        QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6);
+        QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9);
+        mappings[3.0] = 6;
+        mappings[3.5] = 7;
+        AutoRangeMapper rm2(mappings, "x");
+        QCOMPARE(rm2.getUnit(), QString("x"));
+        QCOMPARE(rm2.getType(), AutoRangeMapper::StraightLine);
+        QCOMPARE(rm2.getPositionForValue(0.5), 1);
+        QCOMPARE(rm2.getPositionForValue(4.0), 8);
+        QCOMPARE(rm2.getPositionForValue(3.0), 6);
+        QCOMPARE(rm2.getPositionForValue(3.1), 6);
     }
 
     void autoLogForward()
     {
-	AutoRangeMapper::CoordMap mappings;
-	mappings[10] = 3;
-	mappings[1000] = 5;
-	mappings[100000] = 7;
-	AutoRangeMapper rm1(mappings, "x");
-	QCOMPARE(rm1.getUnit(), QString("x"));
-	QCOMPARE(rm1.getType(), AutoRangeMapper::Logarithmic);
-	QCOMPARE(rm1.getPositionForValue(10.0), 3);
-	QCOMPARE(rm1.getPositionForValue(100000.0), 7);
-	QCOMPARE(rm1.getPositionForValue(1.0), 3);
-	QCOMPARE(rm1.getPositionForValue(1000000.0), 7);
-	QCOMPARE(rm1.getPositionForValue(1000.0), 5);
-	QCOMPARE(rm1.getPositionForValue(900.0), 5);
-	QCOMPARE(rm1.getPositionForValue(20000), 6);
-	QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2);
-	QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5);
-	QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8);
-	mappings[100] = 4;
-	AutoRangeMapper rm2(mappings, "x");
-	QCOMPARE(rm2.getUnit(), QString("x"));
-	QCOMPARE(rm2.getType(), AutoRangeMapper::Logarithmic);
-	QCOMPARE(rm2.getPositionForValue(10.0), 3);
-	QCOMPARE(rm2.getPositionForValue(100000.0), 7);
-	QCOMPARE(rm2.getPositionForValue(1.0), 3);
-	QCOMPARE(rm2.getPositionForValue(1000000.0), 7);
-	QCOMPARE(rm2.getPositionForValue(1000.0), 5);
-	QCOMPARE(rm2.getPositionForValue(900.0), 5);
-	QCOMPARE(rm2.getPositionForValue(20000), 6);
+        AutoRangeMapper::CoordMap mappings;
+        mappings[10] = 3;
+        mappings[1000] = 5;
+        mappings[100000] = 7;
+        AutoRangeMapper rm1(mappings, "x");
+        QCOMPARE(rm1.getUnit(), QString("x"));
+        QCOMPARE(rm1.getType(), AutoRangeMapper::Logarithmic);
+        QCOMPARE(rm1.getPositionForValue(10.0), 3);
+        QCOMPARE(rm1.getPositionForValue(100000.0), 7);
+        QCOMPARE(rm1.getPositionForValue(1.0), 3);
+        QCOMPARE(rm1.getPositionForValue(1000000.0), 7);
+        QCOMPARE(rm1.getPositionForValue(1000.0), 5);
+        QCOMPARE(rm1.getPositionForValue(900.0), 5);
+        QCOMPARE(rm1.getPositionForValue(20000), 6);
+        QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2);
+        QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5);
+        QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8);
+        mappings[100] = 4;
+        AutoRangeMapper rm2(mappings, "x");
+        QCOMPARE(rm2.getUnit(), QString("x"));
+        QCOMPARE(rm2.getType(), AutoRangeMapper::Logarithmic);
+        QCOMPARE(rm2.getPositionForValue(10.0), 3);
+        QCOMPARE(rm2.getPositionForValue(100000.0), 7);
+        QCOMPARE(rm2.getPositionForValue(1.0), 3);
+        QCOMPARE(rm2.getPositionForValue(1000000.0), 7);
+        QCOMPARE(rm2.getPositionForValue(1000.0), 5);
+        QCOMPARE(rm2.getPositionForValue(900.0), 5);
+        QCOMPARE(rm2.getPositionForValue(20000), 6);
     }
 
     void autoInterpolatingForward()
     {
-	AutoRangeMapper::CoordMap mappings;
-	mappings[1] = 10;
-	mappings[3] = 30;
-	mappings[5] = 70;
-	AutoRangeMapper rm(mappings, "x");
-	QCOMPARE(rm.getUnit(), QString("x"));
-	QCOMPARE(rm.getType(), AutoRangeMapper::Interpolating);
-	QCOMPARE(rm.getPositionForValue(1.0), 10);
-	QCOMPARE(rm.getPositionForValue(0.0), 10);
-	QCOMPARE(rm.getPositionForValue(5.0), 70);
-	QCOMPARE(rm.getPositionForValue(6.0), 70);
-	QCOMPARE(rm.getPositionForValue(3.0), 30);
-	QCOMPARE(rm.getPositionForValue(2.5), 25);
-	QCOMPARE(rm.getPositionForValue(4.5), 60);
-	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
-	QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70);
-	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
+        AutoRangeMapper::CoordMap mappings;
+        mappings[1] = 10;
+        mappings[3] = 30;
+        mappings[5] = 70;
+        AutoRangeMapper rm(mappings, "x");
+        QCOMPARE(rm.getUnit(), QString("x"));
+        QCOMPARE(rm.getType(), AutoRangeMapper::Interpolating);
+        QCOMPARE(rm.getPositionForValue(1.0), 10);
+        QCOMPARE(rm.getPositionForValue(0.0), 10);
+        QCOMPARE(rm.getPositionForValue(5.0), 70);
+        QCOMPARE(rm.getPositionForValue(6.0), 70);
+        QCOMPARE(rm.getPositionForValue(3.0), 30);
+        QCOMPARE(rm.getPositionForValue(2.5), 25);
+        QCOMPARE(rm.getPositionForValue(4.5), 60);
+        QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+        QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70);
+        QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
     }
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestScaleTickIntervals.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,622 @@
+/* -*- 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_SCALE_TICK_INTERVALS_H
+#define TEST_SCALE_TICK_INTERVALS_H
+
+#include "../ScaleTickIntervals.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestScaleTickIntervals : public QObject
+{
+    Q_OBJECT
+
+    void printDiff(vector<ScaleTickIntervals::Tick> ticks,
+                   vector<ScaleTickIntervals::Tick> expected) {
+
+        SVCERR << "Have " << ticks.size() << " ticks, expected "
+               << expected.size() << endl;
+        for (int i = 0; i < int(ticks.size()); ++i) {
+            SVCERR << i << ": have " << ticks[i].value << " \""
+                   << ticks[i].label << "\", expected ";
+            if (i < int(expected.size())) {
+                SVCERR << expected[i].value << " \"" << expected[i].label
+                       << "\"" << endl;
+            } else {
+                SVCERR << "(n/a)" << endl;
+            }
+        }
+    }
+    
+    void compareTicks(ScaleTickIntervals::Ticks ticks,
+                      ScaleTickIntervals::Ticks expected,
+                      bool fuzzier = false)
+    {
+        for (int i = 0; i < int(expected.size()); ++i) {
+            if (i < int(ticks.size())) {
+                bool pass = true;
+                if (ticks[i].label != expected[i].label) {
+                    pass = false;
+                } else {
+                    double eps = fuzzier ? 1e-5 : 1e-10;
+                    double diff = fabs(ticks[i].value - expected[i].value);
+                    double limit = max(eps, fabs(ticks[i].value) * eps);
+                    if (diff > limit) {
+                        pass = false;
+                    }
+                }
+                if (!pass) {
+                    printDiff(ticks, expected);
+                }
+                QCOMPARE(ticks[i].label, expected[i].label);
+                QCOMPARE(ticks[i].value, expected[i].value);
+            }
+        }
+        if (ticks.size() != expected.size()) {
+            printDiff(ticks, expected);
+        }
+        QCOMPARE(ticks.size(), expected.size());
+    }
+    
+private slots:
+    void linear_0_1_10()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 1, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0, "0.0" },
+            { 0.1, "0.1" },
+            { 0.2, "0.2" },
+            { 0.3, "0.3" },
+            { 0.4, "0.4" },
+            { 0.5, "0.5" },
+            { 0.6, "0.6" },
+            { 0.7, "0.7" },
+            { 0.8, "0.8" },
+            { 0.9, "0.9" },
+            { 1.0, "1.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_5_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 5, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0, "0" },
+            { 1, "1" },
+            { 2, "2" },
+            { 3, "3" },
+            { 4, "4" },
+            { 5, "5" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_10_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 10, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0, "0" },
+            { 2, "2" },
+            { 4, "4" },
+            { 6, "6" },
+            { 8, "8" },
+            { 10, "10" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_10_0_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 10, 0, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0, "0" },
+            { 2, "2" },
+            { 4, "4" },
+            { 6, "6" },
+            { 8, "8" },
+            { 10, "10" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_m10_0_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ -10, 0, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { -10, "-10" },
+            { -8, "-8" },
+            { -6, "-6" },
+            { -4, "-4" },
+            { -2, "-2" },
+            { 0, "0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_m10_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, -10, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { -10, "-10" },
+            { -8, "-8" },
+            { -6, "-6" },
+            { -4, "-4" },
+            { -2, "-2" },
+            { 0, "0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_0p1_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 0.1, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.00, "0.00" },
+            { 0.02, "0.02" },
+            { 0.04, "0.04" },
+            { 0.06, "0.06" },
+            { 0.08, "0.08" },
+            { 0.10, "0.10" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_0p01_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 0.01, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.000, "0.000" },
+            { 0.002, "0.002" },
+            { 0.004, "0.004" },
+            { 0.006, "0.006" },
+            { 0.008, "0.008" },
+            { 0.010, "0.010" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_0p005_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 0.005, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.000, "0.000" },
+            { 0.001, "0.001" },
+            { 0.002, "0.002" },
+            { 0.003, "0.003" },
+            { 0.004, "0.004" },
+            { 0.005, "0.005" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0_0p001_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 0.001, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0000, "0.0e+00" },
+            { 0.0002, "2.0e-04" },
+            { 0.0004, "4.0e-04" },
+            { 0.0006, "6.0e-04" },
+            { 0.0008, "8.0e-04" },
+            { 0.0010, "1.0e-03" }
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_1_1p001_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 1, 1.001, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 1.0000, "1.0000" },
+            { 1.0002, "1.0002" },
+            { 1.0004, "1.0004" },
+            { 1.0006, "1.0006" },
+            { 1.0008, "1.0008" },
+            { 1.0010, "1.0010" }
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_0p001_1_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0.001, 1, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.1, "0.1" },
+            { 0.3, "0.3" },
+            { 0.5, "0.5" },
+            { 0.7, "0.7" },
+            { 0.9, "0.9" },
+        };
+        compareTicks(ticks, expected);
+    }
+        
+    void linear_10000_10010_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 10000, 10010, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 10000, "10000" },
+            { 10002, "10002" },
+            { 10004, "10004" },
+            { 10006, "10006" },
+            { 10008, "10008" },
+            { 10010, "10010" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_10000_20000_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 10000, 20000, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 10000, "10000" },
+            { 12000, "12000" },
+            { 14000, "14000" },
+            { 16000, "16000" },
+            { 18000, "18000" },
+            { 20000, "20000" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_m1_1_10()
+    {
+        auto ticks = ScaleTickIntervals::linear({ -1, 1, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { -1.0, "-1.0" },
+            { -0.8, "-0.8" },
+            { -0.6, "-0.6" },
+            { -0.4, "-0.4" },
+            { -0.2, "-0.2" },
+            { 0.0, "0.0" },
+            { 0.2, "0.2" },
+            { 0.4, "0.4" },
+            { 0.6, "0.6" },
+            { 0.8, "0.8" },
+            { 1.0, "1.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_221p23_623p7_57p4()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 221.23, 623.7, 4 });
+        // only 4 ticks, not 5, because none of the rounded tick
+        // values lies on an end value
+        ScaleTickIntervals::Ticks expected {
+            { 300, "300" },
+            { 400, "400" },
+            { 500, "500" },
+            { 600, "600" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_sqrt2_pi_7()
+    {
+        auto ticks = ScaleTickIntervals::linear({ sqrt(2.0), M_PI, 7 });
+        // This would be better in steps of 0.25, but we only round to
+        // integral powers of ten
+        ScaleTickIntervals::Ticks expected {
+            { 1.5, "1.5" },
+            { 1.7, "1.7" },
+            { 1.9, "1.9" },
+            { 2.1, "2.1" },
+            { 2.3, "2.3" },
+            { 2.5, "2.5" },
+            { 2.7, "2.7" },
+            { 2.9, "2.9" },
+            { 3.1, "3.1" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_pi_avogadro_7()
+    {
+        auto ticks = ScaleTickIntervals::linear({ M_PI, 6.022140857e23, 7 });
+        ScaleTickIntervals::Ticks expected {
+            // not perfect, but ok-ish
+            { 0, "0.0e+00" },
+            { 9e+22, "9.0e+22" },
+            { 1.8e+23, "1.8e+23" },
+            { 2.7e+23, "2.7e+23" },
+            { 3.6e+23, "3.6e+23" },
+            { 4.5e+23, "4.5e+23" },
+            { 5.4e+23, "5.4e+23" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_1()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 1 });
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2" },
+            { 3.0, "3" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_2()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 2 });
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2.0" },
+            { 2.5, "2.5" },
+            { 3.0, "3.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_3()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 3 });
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2.0" },
+            { 2.3, "2.3" },
+            { 2.6, "2.6" },
+            { 2.9, "2.9" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_4()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 4 });
+        // This would be better in steps of 0.25, but we only round to
+        // integral powers of ten
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2.0" },
+            { 2.3, "2.3" },
+            { 2.6, "2.6" },
+            { 2.9, "2.9" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_5()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 5 });
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2.0" },
+            { 2.2, "2.2" },
+            { 2.4, "2.4" },
+            { 2.6, "2.6" },
+            { 2.8, "2.8" },
+            { 3.0, "3.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_2_3_6()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 2, 3, 6 });
+        ScaleTickIntervals::Ticks expected {
+            { 2.0, "2.0" },
+            { 2.2, "2.2" },
+            { 2.4, "2.4" },
+            { 2.6, "2.6" },
+            { 2.8, "2.8" },
+            { 3.0, "3.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_1_1_10()
+    {
+        // pathological range
+        auto ticks = ScaleTickIntervals::linear({ 1, 1, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 1.0, "1" }
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_0_0_10()
+    {
+        // pathological range
+        auto ticks = ScaleTickIntervals::linear({ 0, 0, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0, "0.0" }
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_0_1_1()
+    {
+        auto ticks = ScaleTickIntervals::linear({ 0, 1, 1 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0, "0" },
+            { 1.0, "1" }
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_0_1_0()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::linear({ 0, 1, 0 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0, "0.0" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void linear_0_1_m1()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::linear({ 0, 1, -1 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.0, "0.0" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void linear_0p465_778_10()
+    {
+        // a case that gave unsatisfactory results in real life
+        // (initially it had the first tick at 1)
+        auto ticks = ScaleTickIntervals::linear({ 0.465, 778.08, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 10, "10" },
+            { 90, "90" },
+            { 170, "170" },
+            { 250, "250" },
+            { 330, "330" },
+            { 410, "410" },
+            { 490, "490" },
+            { 570, "570" },
+            { 650, "650" },
+            { 730, "730" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void log_1_10_2()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 1, 10, 2 });
+        ScaleTickIntervals::Ticks expected {
+            { 1.0, "1.0" },
+            { 3.2, "3.2" },
+            { 10.0, "10" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void log_0_10_2()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 0, 10, 2 });
+        ScaleTickIntervals::Ticks expected {
+            { 1e-6, "1e-06" },
+            { 1, "1" },
+        };
+        compareTicks(ticks, expected);
+    }
+
+    void log_pi_avogadro_7()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ M_PI, 6.022140857e23, 7 });
+        ScaleTickIntervals::Ticks expected {
+            { 1000, "1000" },
+            { 1e+06, "1e+06" },
+            { 1e+09, "1e+09" },
+            { 1e+12, "1e+12" },
+            { 1e+15, "1e+15" },
+            { 1e+18, "1e+18" },
+            { 1e+21, "1e+21" },
+        };
+        compareTicks(ticks, expected, true);
+    }
+
+    void log_0p465_778_10()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 0.465, 778.08, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 0.5, "0.50" },
+            { 1, "1.0" },
+            { 2, "2.0" },
+            { 4, "4.0" },
+            { 8, "8.0" },
+            { 16, "16" },
+            { 32, "32" },
+            { 64, "64" },
+            { 130, "130" },
+            { 260, "260" },
+            { 510, "510" },
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void log_1_10k_10()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 1.0, 10000.0, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 1.0, "1.0" },
+            { 2.5, "2.5" },
+            { 6.3, "6.3" },
+            { 16.0, "16" },
+            { 40.0, "40" },
+            { 100.0, "100" },
+            { 250.0, "250" },
+            { 630.0, "630" },
+            { 1600.0, "1600" },
+            { 4000.0, "4000" },
+            { 10000.0, "1e+04" },
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_80_10k_6()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 80.0, 10000.0, 6 });
+        ScaleTickIntervals::Ticks expected {
+            { 130, "130" },
+            { 260, "260" },
+            { 510, "510" },
+            { 1000, "1000" },
+            { 2000, "2000" },
+            { 4100, "4100" },
+            { 8200, "8200" }
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_80_800k_10()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 80.0, 800000.0, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 100, "100" },
+            { 250, "250" },
+            { 630, "630" },
+            { 1600, "1600" },
+            { 4000, "4000" },
+            { 10000, "1e+04" },
+            { 25000, "2.5e+04" },
+            { 63000, "6.3e+04" },
+            { 160000, "1.6e+05" },
+            { 400000, "4e+05" },
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_0_1_0()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::logarithmic({ 0, 1, 0 });
+        ScaleTickIntervals::Ticks expected {
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void log_0_1_m1()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::logarithmic({ 0, 1, -1 });
+        ScaleTickIntervals::Ticks expected {
+        };
+        compareTicks(ticks, expected);
+    }
+
+};
+
+#endif
+
+
--- a/base/test/TestStringBits.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestStringBits.h	Mon Sep 17 13:51:14 2018 +0100
@@ -128,6 +128,34 @@
         testSplitQuoted(in, out);
     }
 
+    void snested3() {
+        QString in = "'aa bb cc\"' dd";
+        QStringList out;            
+        out << "aa bb cc\"" << "dd";
+        testSplitQuoted(in, out);
+    }
+
+    void snested3a() {
+        QString in = "\"aa bb cc'\" dd";
+        QStringList out;            
+        out << "aa bb cc'" << "dd";
+        testSplitQuoted(in, out);
+    }
+
+    void snested4() {
+        QString in = "'aa \"bb cc\" dd'";
+        QStringList out;            
+        out << "aa \"bb cc\" dd";
+        testSplitQuoted(in, out);
+    }
+
+    void snested4a() {
+        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;                 
@@ -135,6 +163,19 @@
         testSplitQuoted(in, out);
     }
 
+    void qspace() {
+        QString in = "\"a a\":\"b:b\":\"c d\"";
+        QStringList out1;
+        // Can't start a quote in the middle of a bare field - they
+        // are handled only if the first character in the field is a
+        // quote. Otherwise we'd have trouble with apostrophes etc
+        out1 << "a a:\"b:b\":\"c" << "d\"";
+        QCOMPARE(StringBits::splitQuoted(in, ' '), out1);
+        QStringList out2;
+        out2 << "a a" << "b:b" << "c d";
+        QCOMPARE(StringBits::splitQuoted(in, ':'), out2);
+    }
+    
     void multispace() {
         QString in = "  a'a \\'         'bb'    '      \\\"cc\" ' dd\\\" '";
         QStringList out;                                            
--- a/base/test/TestVampRealTime.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/TestVampRealTime.h	Mon Sep 17 13:51:14 2018 +0100
@@ -32,223 +32,227 @@
     Q_OBJECT
 
     void compareTexts(string s, const char *e) {
-        QCOMPARE(QString(s.c_str()), QString(e));
+        QString actual(s.c_str());
+        QString expected(e);
+        QCOMPARE(actual, expected);
     }
 
     typedef Vamp::RealTime RealTime;
     typedef long frame_type;
-			   
+                           
 private slots:
 
     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);
+        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));
+        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));
+        // 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(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(-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), 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);
+        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), 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(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));
+        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));
 
-    	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));
     }
-    
+
+#ifndef Q_OS_WIN
     void fromTimeval()
     {
-	struct timeval tv;
+        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 = 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));
+        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));
     }
+#endif
 
     void assign()
     {
-	RealTime r;
-	r = RealTime(0, 0);
-	QCOMPARE(r, RealTime::zeroTime);
-	r = RealTime(0, ONE_BILLION/2);
+        RealTime r;
+        r = RealTime(0, 0);
+        QCOMPARE(r, RealTime::zeroTime);
+        r = RealTime(0, ONE_BILLION/2);
         QCOMPARE(r.sec, 0);
         QCOMPARE(r.nsec, ONE_BILLION/2);
-	r = RealTime(-1, -ONE_BILLION/2);
+        r = RealTime(-1, -ONE_BILLION/2);
         QCOMPARE(r.sec, -1);
         QCOMPARE(r.nsec, -ONE_BILLION/2);
     }
 
     void plus()
     {
-	QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0));
+        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(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(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));
+        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, 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(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(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));
+        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));
+        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) {
+        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);
+                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(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);
+                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;
-	    }
-	}
+                prev = curr;
+            }
+        }
     }
 
     void frame()
--- a/base/test/files.pri	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/files.pri	Mon Sep 17 13:51:14 2018 +0100
@@ -1,10 +1,12 @@
 TEST_HEADERS = \
+	     TestColumnOp.h \
+	     TestLogRange.h \
 	     TestRangeMapper.h \
+	     TestOurRealTime.h \
 	     TestPitch.h \
-	     TestOurRealTime.h \
-	     TestVampRealTime.h \
+	     TestScaleTickIntervals.h \
 	     TestStringBits.h \
-	     TestColumnOp.h
+	     TestVampRealTime.h
 	     
 TEST_SOURCES += \
 	     svcore-base-test.cpp
--- a/base/test/svcore-base-test.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/base/test/svcore-base-test.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -11,8 +11,10 @@
     COPYING included with this distribution for more information.
 */
 
+#include "TestLogRange.h"
 #include "TestRangeMapper.h"
 #include "TestPitch.h"
+#include "TestScaleTickIntervals.h"
 #include "TestStringBits.h"
 #include "TestOurRealTime.h"
 #include "TestVampRealTime.h"
@@ -27,45 +29,55 @@
     int good = 0, bad = 0;
 
     QCoreApplication app(argc, argv);
-    app.setOrganizationName("Sonic Visualiser");
+    app.setOrganizationName("sonic-visualiser");
     app.setApplicationName("test-svcore-base");
 
     {
-	TestRangeMapper t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        TestRangeMapper t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
     }
     {
-	TestPitch t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        TestPitch t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
     }
     {
         TestOurRealTime t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
     }
     {
         TestVampRealTime t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
     }
     {
-	TestStringBits t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        TestStringBits t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
     }
     {
-	TestColumnOp t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        TestColumnOp t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+    {
+        TestLogRange t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+    {
+        TestScaleTickIntervals 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;
+        SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+        return 1;
     } else {
-	cerr << "All tests passed" << endl;
-	return 0;
+        SVCERR << "All tests passed" << endl;
+        return 0;
     }
 }
--- a/data/fileio/AudioFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/AudioFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -17,17 +17,17 @@
 
 using std::vector;
 
-vector<vector<float>>
+vector<floatvec_t>
 AudioFileReader::getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
-    vector<float> interleaved = getInterleavedFrames(start, count);
+    floatvec_t interleaved = getInterleavedFrames(start, count);
     
     int channels = getChannelCount();
     if (channels == 1) return { interleaved };
     
     sv_frame_t rc = interleaved.size() / channels;
 
-    vector<vector<float>> frames(channels, vector<float>(rc, 0.f));
+    vector<floatvec_t> frames(channels, floatvec_t(rc, 0.f));
     
     for (int c = 0; c < channels; ++c) {
         for (sv_frame_t i = 0; i < rc; ++i) {
--- a/data/fileio/AudioFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/AudioFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -117,8 +117,8 @@
      * thread-safe -- that is, safe to call from multiple threads with
      * different arguments on the same object at the same time.
      */
-    virtual std::vector<float> getInterleavedFrames(sv_frame_t start,
-                                                    sv_frame_t count) const = 0;
+    virtual floatvec_t getInterleavedFrames(sv_frame_t start,
+                                            sv_frame_t count) const = 0;
 
     /**
      * Return de-interleaved samples for count frames from index
@@ -127,8 +127,8 @@
      * will contain getChannelCount() sample blocks of count samples
      * each (or fewer if end of file is reached).
      */
-    virtual std::vector<std::vector<float> > getDeInterleavedFrames(sv_frame_t start,
-                                                                    sv_frame_t count) const;
+    virtual std::vector<floatvec_t> 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
--- a/data/fileio/AudioFileReaderFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -19,7 +19,6 @@
 #include "DecodingWavFileReader.h"
 #include "OggVorbisFileReader.h"
 #include "MP3FileReader.h"
-#include "QuickTimeFileReader.h"
 #include "CoreAudioFileReader.h"
 #include "AudioFileSizeEstimator.h"
 
@@ -43,9 +42,6 @@
     OggVorbisFileReader::getSupportedExtensions(extensions);
 #endif
 #endif
-#ifdef HAVE_QUICKTIME
-    QuickTimeFileReader::getSupportedExtensions(extensions);
-#endif
 #ifdef HAVE_COREAUDIO
     CoreAudioFileReader::getSupportedExtensions(extensions);
 #endif
@@ -67,15 +63,16 @@
 {
     QString err;
 
-    SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << params.targetRate << (params.targetRate == 0 ? " (use source rate)" : "") << endl;
+    SVDEBUG << "AudioFileReaderFactory: url \"" << source.getLocation() << "\": requested rate: " << params.targetRate << (params.targetRate == 0 ? " (use source rate)" : "") << endl;
+    SVDEBUG << "AudioFileReaderFactory: local filename \"" << source.getLocalFilename() << "\", content type \"" << source.getContentType() << "\"" << endl;
 
     if (!source.isOK()) {
-        SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl;
+        SVCERR << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl;
         return 0;
     }
 
     if (!source.isAvailable()) {
-        SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
+        SVCERR << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
         return 0;
     }
 
@@ -92,11 +89,16 @@
 
     if (estimatedSamples > 0) {
         size_t kb = (estimatedSamples * sizeof(float)) / 1024;
+        SVDEBUG << "AudioFileReaderFactory: checking where to potentially cache "
+                << kb << "K of sample data" << endl;
         StorageAdviser::Recommendation rec =
             StorageAdviser::recommend(StorageAdviser::SpeedCritical, kb, kb);
         if ((rec & StorageAdviser::UseMemory) ||
             (rec & StorageAdviser::PreferMemory)) {
+            SVDEBUG << "AudioFileReaderFactory: cacheing (if at all) in memory" << endl;
             cacheMode = CodedAudioFileReader::CacheInMemory;
+        } else {
+            SVDEBUG << "AudioFileReaderFactory: cacheing (if at all) on disc" << endl;
         }
     }
     
@@ -118,6 +120,34 @@
 
         bool anyReader = (any > 0);
 
+        if (!anyReader) {
+            SVDEBUG << "AudioFileReaderFactory: Checking whether any reader officially handles this source" << endl;
+        } else {
+            SVDEBUG << "AudioFileReaderFactory: Source not officially handled by any reader, trying again with each reader in turn"
+                    << endl;
+        }
+    
+#ifdef HAVE_OGGZ
+#ifdef HAVE_FISHSOUND
+        // If we have the "real" Ogg reader, use that first. Otherwise
+        // the WavFileReader will likely accept Ogg files (as
+        // libsndfile supports them) but it has no ability to return
+        // file metadata, so we get a slightly less useful result.
+        if (anyReader || OggVorbisFileReader::supports(source)) {
+
+            reader = new OggVorbisFileReader
+                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+
+            if (reader->isOK()) {
+                SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl;
+                return reader;
+            } else {
+                delete reader;
+            }
+        }
+#endif
+#endif
+
         if (anyReader || WavFileReader::supports(source)) {
 
             reader = new WavFileReader(source);
@@ -130,7 +160,7 @@
                  (cacheMode == CodedAudioFileReader::CacheInMemory) ||
                  (targetRate != 0 && fileRate != targetRate))) {
 
-                SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
+                SVDEBUG << "AudioFileReaderFactory: WAV file reader rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
             
                 delete reader;
                 reader = new DecodingWavFileReader
@@ -142,27 +172,12 @@
             }
 
             if (reader->isOK()) {
+                SVDEBUG << "AudioFileReaderFactory: WAV file reader is OK, returning it" << endl;
                 return reader;
             } else {
                 delete reader;
             }
         }
-    
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-        if (anyReader || OggVorbisFileReader::supports(source)) {
-
-            reader = new OggVorbisFileReader
-                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
-
-            if (reader->isOK()) {
-                return reader;
-            } else {
-                delete reader;
-            }
-        }
-#endif
-#endif
 
 #ifdef HAVE_MAD
         if (anyReader || MP3FileReader::supports(source)) {
@@ -177,20 +192,7 @@
                  targetRate, normalised, reporter);
 
             if (reader->isOK()) {
-                return reader;
-            } else {
-                delete reader;
-            }
-        }
-#endif
-
-#ifdef HAVE_QUICKTIME
-        if (anyReader || QuickTimeFileReader::supports(source)) {
-
-            reader = new QuickTimeFileReader
-                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
-
-            if (reader->isOK()) {
+                SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl;
                 return reader;
             } else {
                 delete reader;
@@ -205,6 +207,7 @@
                 (source, decodeMode, cacheMode, targetRate, normalised, reporter);
 
             if (reader->isOK()) {
+                SVDEBUG << "AudioFileReaderFactory: CoreAudio reader is OK, returning it" << endl;
                 return reader;
             } else {
                 delete reader;
@@ -214,10 +217,11 @@
 
     }
     
-    SVDEBUG << "AudioFileReaderFactory::Failed to create a reader for "
-            << "url \"" << source.getLocation()
-            << "\" (content type \""
-            << source.getContentType() << "\")" << endl;
+    SVCERR << "AudioFileReaderFactory::Failed to create a reader for "
+           << "url \"" << source.getLocation()
+           << "\" (local filename \"" << source.getLocalFilename()
+           << "\", content type \""
+           << source.getContentType() << "\")" << endl;
     return nullptr;
 }
 
--- a/data/fileio/AudioFileSizeEstimator.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/AudioFileSizeEstimator.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -18,90 +18,95 @@
 
 #include <QFile>
 
-//#define DEBUG_AUDIO_FILE_SIZE_ESTIMATOR 1
+#include "base/Debug.h"
 
 sv_frame_t
 AudioFileSizeEstimator::estimate(FileSource source,
-				 sv_samplerate_t targetRate)
+                                 sv_samplerate_t targetRate)
 {
     sv_frame_t estimate = 0;
     
+    SVDEBUG << "AudioFileSizeEstimator: Sample count estimate requested for file \""
+            << source.getLocalFilename() << "\"" << endl;
+
     // 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;
+        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);
+        }
+        SVDEBUG << "AudioFileSizeEstimator: WAV file reader accepts this file, reports "
+                << samples << " samples" << endl;
+        estimate = samples;
+    } else {
+        SVDEBUG << "AudioFileSizeEstimator: WAV file reader doesn't like this file, "
+                << "estimating from file size and extension instead" << endl;
     }
 
+    delete reader;
+    reader = 0;
+
     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;
-	}
+        // 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();
+        QString extension = source.getExtension();
 
-	source.waitForData();
-	if (!source.isOK()) return 0;
+        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();
-	    }
-	}
+        sv_frame_t sz = 0;
 
-	if (extension == "ogg" || extension == "oga" ||
-	    extension == "m4a" || extension == "mp3" ||
-	    extension == "wma") {
+        {
+            QFile f(source.getLocalFilename());
+            if (f.open(QFile::ReadOnly)) {
+                SVDEBUG << "AudioFileSizeEstimator: opened file, size is "
+                        << f.size() << endl;
+                sz = f.size();
+                f.close();
+            }
+        }
 
-	    // 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.
+        if (extension == "ogg" || extension == "oga" ||
+            extension == "m4a" || extension == "mp3" ||
+            extension == "wma") {
 
-	    estimate = sv_frame_t(double(sz) * 10 * rateRatio);
-	}
+            // 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.
 
-	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) * 10 * rateRatio);
+        }
 
-	    estimate = sv_frame_t(double(sz) * 1.2 * 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.
 
-#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR
-	cerr << "AudioFileSizeEstimator: for extension " << extension << ", estimate = " << estimate << endl;
-#endif
+            estimate = sv_frame_t(double(sz) * 1.2 * rateRatio);
+        }
+
+        SVDEBUG << "AudioFileSizeEstimator: for extension \""
+                << extension << "\", estimate = " << estimate << " samples" << endl;
     }
-
-#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR
-    cerr << "estimate = " << estimate << endl;
-#endif
     
     return estimate;
 }
--- a/data/fileio/AudioFileSizeEstimator.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/AudioFileSizeEstimator.h	Mon Sep 17 13:51:14 2018 +0100
@@ -43,7 +43,7 @@
      * will return 0.
      */
     static sv_frame_t estimate(FileSource source,
-			       sv_samplerate_t targetRate = 0);
+                               sv_samplerate_t targetRate = 0);
 };
 
 #endif
--- a/data/fileio/BZipFileDevice.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/BZipFileDevice.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -21,8 +21,16 @@
 
 #include "base/Debug.h"
 
+// for dup:
+#ifdef _MSC_VER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
 BZipFileDevice::BZipFileDevice(QString fileName) :
     m_fileName(fileName),
+    m_qfile(fileName),
     m_file(0),
     m_bzFile(0),
     m_atEnd(true),
@@ -70,11 +78,39 @@
         return false;
     }
 
+    // This is all going to be a bit silly.
+    //
+    // We open the file with QFile so as not to have to worry about locale
+    // support ourselves (especially on Windows). Then we get a fd from
+    // QFile and "convert" it to a FILE* using fdopen because that is what
+    // the bz2 library needs for reading and writing an already-open file.
+    //
+    // fdopen takes over the fd it is given, and will close it when fclose
+    // is called. (We must call fclose, because it's needed to avoid
+    // leaking the file stream structure.)
+    //
+    // But QFile will also close its fd, either when we call QFile::close
+    // or on destruction -- there doesn't seem to be a way to avoid that
+    // for a file that QFile opened.
+    //
+    // So we have to add an extra dup() in to the fdopen to avoid a double
+    // close.
+    //
+    // Note that bz2 will *not* fclose the FILE* it was passed, so we
+    // don't have a problem with calling both bzWriteClose and fclose.
+
     if (mode & WriteOnly) {
 
-        m_file = fopen(m_fileName.toLocal8Bit().data(), "wb");
+        if (!m_qfile.open(QIODevice::WriteOnly)) {
+            setErrorString(tr("Failed to open file for writing"));
+            m_ok = false;
+            return false;
+        }
+        
+        m_file = fdopen(dup(m_qfile.handle()), "wb");
         if (!m_file) {
-            setErrorString(tr("Failed to open file for writing"));
+            setErrorString(tr("Failed to open file handle for writing"));
+            m_qfile.close();
             m_ok = false;
             return false;
         }
@@ -85,6 +121,7 @@
         if (!m_bzFile) {
             fclose(m_file);
             m_file = 0;
+            m_qfile.close();
             setErrorString(tr("Failed to open bzip2 stream for writing"));
             m_ok = false;
             return false;
@@ -99,9 +136,15 @@
 
     if (mode & ReadOnly) {
 
-        m_file = fopen(m_fileName.toLocal8Bit().data(), "rb");
+        if (!m_qfile.open(QIODevice::ReadOnly)) {
+            setErrorString(tr("Failed to open file for reading"));
+            m_ok = false;
+            return false;
+        }
+        
+        m_file = fdopen(dup(m_qfile.handle()), "rb");
         if (!m_file) {
-            setErrorString(tr("Failed to open file for reading"));
+            setErrorString(tr("Failed to open file handle for reading"));
             m_ok = false;
             return false;
         }
@@ -112,6 +155,7 @@
         if (!m_bzFile) {
             fclose(m_file);
             m_file = 0;
+            m_qfile.close();
             setErrorString(tr("Failed to open bzip2 stream for reading"));
             m_ok = false;
             return false;
@@ -145,11 +189,12 @@
     if (openMode() & WriteOnly) {
         unsigned int in = 0, out = 0;
         BZ2_bzWriteClose(&bzError, m_bzFile, 0, &in, &out);
-//	cerr << "Wrote bzip2 stream (in=" << in << ", out=" << out << ")" << endl;
-	if (bzError != BZ_OK) {
-	    setErrorString(tr("bzip2 stream write close error"));
-	}
+//        cerr << "Wrote bzip2 stream (in=" << in << ", out=" << out << ")" << endl;
+        if (bzError != BZ_OK) {
+            setErrorString(tr("bzip2 stream write close error"));
+        }
         fclose(m_file);
+        m_qfile.close();
         m_bzFile = 0;
         m_file = 0;
         m_ok = false;
@@ -162,6 +207,7 @@
             setErrorString(tr("bzip2 stream read close error"));
         }
         fclose(m_file);
+        m_qfile.close();
         m_bzFile = 0;
         m_file = 0;
         m_ok = false;
--- a/data/fileio/BZipFileDevice.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/BZipFileDevice.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,10 +13,11 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _BZIP_FILE_DEVICE_H_
-#define _BZIP_FILE_DEVICE_H_
+#ifndef SV_BZIP_FILE_DEVICE_H
+#define SV_BZIP_FILE_DEVICE_H
 
 #include <QIODevice>
+#include <QFile>
 
 #include <bzlib.h>
 
@@ -41,6 +42,7 @@
 
     QString m_fileName;
 
+    QFile m_qfile;
     FILE *m_file;
     BZFILE *m_bzFile;
     bool m_atEnd;
--- a/data/fileio/CSVFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -18,60 +18,79 @@
 #include "model/Model.h"
 #include "base/RealTime.h"
 #include "base/StringBits.h"
+#include "base/ProgressReporter.h"
+#include "base/RecordDirectory.h"
 #include "model/SparseOneDimensionalModel.h"
 #include "model/SparseTimeValueModel.h"
 #include "model/EditableDenseThreeDimensionalModel.h"
 #include "model/RegionModel.h"
 #include "model/NoteModel.h"
+#include "model/WritableWaveFileModel.h"
 #include "DataFileReaderFactory.h"
 
 #include <QFile>
+#include <QDir>
 #include <QFileInfo>
 #include <QString>
 #include <QRegExp>
 #include <QStringList>
 #include <QTextStream>
+#include <QDateTime>
 
 #include <iostream>
 #include <map>
+#include <string>
 
 using namespace std;
 
 CSVFileReader::CSVFileReader(QString path, CSVFormat format,
-                             sv_samplerate_t mainModelSampleRate) :
+                             sv_samplerate_t mainModelSampleRate,
+                             ProgressReporter *reporter) :
     m_format(format),
     m_device(0),
     m_ownDevice(true),
     m_warnings(0),
-    m_mainModelSampleRate(mainModelSampleRate)
+    m_mainModelSampleRate(mainModelSampleRate),
+    m_fileSize(0),
+    m_readCount(0),
+    m_progress(-1),
+    m_reporter(reporter)
 {
     QFile *file = new QFile(path);
     bool good = false;
     
     if (!file->exists()) {
-	m_error = QFile::tr("File \"%1\" does not exist").arg(path);
+        m_error = QFile::tr("File \"%1\" does not exist").arg(path);
     } else if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) {
-	m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
+        m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
     } else {
-	good = true;
+        good = true;
     }
 
     if (good) {
         m_device = file;
         m_filename = QFileInfo(path).fileName();
+        m_fileSize = file->size();
+        if (m_reporter) m_reporter->setDefinite(true);
     } else {
-	delete file;
+        delete file;
     }
 }
 
 CSVFileReader::CSVFileReader(QIODevice *device, CSVFormat format,
-                             sv_samplerate_t mainModelSampleRate) :
+                             sv_samplerate_t mainModelSampleRate,
+                             ProgressReporter *reporter) :
     m_format(format),
     m_device(device),
     m_ownDevice(false),
     m_warnings(0),
-    m_mainModelSampleRate(mainModelSampleRate)
+    m_mainModelSampleRate(mainModelSampleRate),
+    m_fileSize(0),
+    m_readCount(0),
+    m_progress(-1),
+    m_reporter(reporter)
 {
+    if (m_reporter) m_reporter->setDefinite(false);
 }
 
 CSVFileReader::~CSVFileReader()
@@ -137,12 +156,12 @@
     
     if (!ok) {
         if (m_warnings < warnLimit) {
-            cerr << "WARNING: CSVFileReader::load: "
+            SVCERR << "WARNING: CSVFileReader::load: "
                       << "Bad time format (\"" << s
                       << "\") in data line "
                       << lineno+1 << endl;
         } else if (m_warnings == warnLimit) {
-            cerr << "WARNING: Too many warnings" << endl;
+            SVCERR << "WARNING: Too many warnings" << endl;
         }
         ++m_warnings;
     }
@@ -172,10 +191,10 @@
         } else {
             windowSize = 1;
         }
-	if (timeUnits == CSVFormat::TimeSeconds ||
+        if (timeUnits == CSVFormat::TimeSeconds ||
             timeUnits == CSVFormat::TimeMilliseconds) {
-	    sampleRate = m_mainModelSampleRate;
-	}
+            sampleRate = m_mainModelSampleRate;
+        }
     }
 
     SparseOneDimensionalModel *model1 = 0;
@@ -183,6 +202,7 @@
     RegionModel *model2a = 0;
     NoteModel *model2b = 0;
     EditableDenseThreeDimensionalModel *model3 = 0;
+    WritableWaveFileModel *modelW = 0;
     Model *model = 0;
 
     QTextStream in(m_device);
@@ -202,8 +222,6 @@
 
     sv_frame_t startFrame = 0; // for calculation of dense model resolution
     bool firstEverValue = true;
-
-    map<QString, int> labelCountMap;
     
     int valueColumns = 0;
     for (int i = 0; i < m_format.getColumnCount(); ++i) {
@@ -212,7 +230,41 @@
         }
     }
 
-    while (!in.atEnd()) {
+    int audioChannels = 0;
+    float **audioSamples = 0;
+    float sampleShift = 0.f;
+    float sampleScale = 1.f;
+
+    if (modelType == CSVFormat::WaveFileModel) {
+
+        audioChannels = valueColumns;
+                
+        audioSamples =
+            breakfastquay::allocate_and_zero_channels<float>
+            (audioChannels, 1);
+
+        switch (m_format.getAudioSampleRange()) {
+        case CSVFormat::SampleRangeSigned1:
+        case CSVFormat::SampleRangeOther:
+            sampleShift = 0.f;
+            sampleScale = 1.f;
+            break;
+        case CSVFormat::SampleRangeUnsigned255:
+            sampleShift = -128.f;
+            sampleScale = 1.f / 128.f;
+            break;
+        case CSVFormat::SampleRangeSigned32767:
+            sampleShift = 0.f;
+            sampleScale = 1.f / 32768.f;
+            break;
+        }
+    }
+
+    map<QString, int> labelCountMap;
+
+    bool abandoned = false;
+    
+    while (!in.atEnd() && !abandoned) {
 
         // QTextStream's readLine doesn't cope with old-style Mac
         // CR-only line endings.  Why did they bother making the class
@@ -227,6 +279,26 @@
 
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
+
+        m_readCount += chunk.size() + 1;
+
+        if (m_reporter) {
+            if (m_reporter->wasCancelled()) {
+                abandoned = true;
+                break;
+            }
+            int progress;
+            if (m_fileSize > 0) {
+                progress = int((double(m_readCount) / double(m_fileSize))
+                               * 100.0);
+            } else {
+                progress = int(m_readCount / 10000);
+            }
+            if (progress != m_progress) {
+                m_reporter->setProgress(progress);
+                m_progress = progress;
+            }
+        }
         
         for (int li = 0; li < lines.size(); ++li) {
 
@@ -237,28 +309,30 @@
             QStringList list = StringBits::split(line, separator, allowQuoting);
             if (!model) {
 
+                QString modelName = m_filename;
+                
                 switch (modelType) {
 
                 case CSVFormat::OneDimensionalModel:
                     model1 = new SparseOneDimensionalModel(sampleRate, windowSize);
                     model = model1;
                     break;
-		
+                
                 case CSVFormat::TwoDimensionalModel:
                     model2 = new SparseTimeValueModel(sampleRate, windowSize, false);
                     model = model2;
                     break;
-		
+                
                 case CSVFormat::TwoDimensionalModelWithDuration:
                     model2a = new RegionModel(sampleRate, windowSize, false);
                     model = model2a;
                     break;
-		
+                
                 case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
                     model2b = new NoteModel(sampleRate, windowSize, false);
                     model = model2b;
                     break;
-		
+                
                 case CSVFormat::ThreeDimensionalModel:
                     model3 = new EditableDenseThreeDimensionalModel
                         (sampleRate,
@@ -267,22 +341,50 @@
                          EditableDenseThreeDimensionalModel::NoCompression);
                     model = model3;
                     break;
+
+                case CSVFormat::WaveFileModel:
+                {
+                    bool normalise = (m_format.getAudioSampleRange()
+                                      == CSVFormat::SampleRangeOther);
+                    QString path = getConvertedAudioFilePath();
+                    modelW = new WritableWaveFileModel
+                        (path, sampleRate, valueColumns,
+                         normalise ?
+                         WritableWaveFileModel::Normalisation::Peak :
+                         WritableWaveFileModel::Normalisation::None);
+                    modelName = QFileInfo(path).fileName();
+                    model = modelW;
+                    break;
+                }
                 }
 
-                if (model) {
-                    if (m_filename != "") {
-                        model->setObjectName(m_filename);
+                if (model && model->isOK()) {
+                    if (modelName != "") {
+                        model->setObjectName(modelName);
                     }
                 }
             }
 
+            if (!model || !model->isOK()) {
+                SVCERR << "Failed to create model to load CSV file into"
+                       << endl;
+                if (model) {
+                    delete model;
+                    model = 0;
+                    model1 = 0; model2 = 0; model2a = 0; model2b = 0;
+                    model3 = 0; modelW = 0;
+                }
+                abandoned = true;
+                break;
+            }
+            
             float value = 0.f;
             float pitch = 0.f;
             QString label = "";
 
             duration = 0.f;
             haveEndTime = false;
-
+            
             for (int i = 0; i < list.size(); ++i) {
 
                 QString s = list[i];
@@ -334,7 +436,7 @@
             }
 
             if (modelType == CSVFormat::OneDimensionalModel) {
-	    
+            
                 SparseOneDimensionalModel::Point point(frameNo, label);
                 model1->addPoint(point);
 
@@ -368,7 +470,7 @@
                     float value = list[i].toFloat(&ok);
 
                     values.push_back(value);
-	    
+            
                     if (firstEverValue || value < min) min = value;
                     if (firstEverValue || value > max) max = value;
                     
@@ -384,25 +486,69 @@
 
                     if (!ok) {
                         if (warnings < warnLimit) {
-                            cerr << "WARNING: CSVFileReader::load: "
+                            SVCERR << "WARNING: CSVFileReader::load: "
                                       << "Non-numeric value \""
                                       << list[i]
                                       << "\" in data line " << lineno+1
                                       << ":" << endl;
-                            cerr << line << endl;
+                            SVCERR << line << endl;
                             ++warnings;
                         } else if (warnings == warnLimit) {
-//                            cerr << "WARNING: Too many warnings" << endl;
+//                            SVCERR << "WARNING: Too many warnings" << endl;
                         }
                     }
                 }
-	
+        
 //                SVDEBUG << "Setting bin values for count " << lineno << ", frame "
 //                          << frameNo << ", time " << RealTime::frame2RealTime(frameNo, sampleRate) << endl;
 
                 model3->setColumn(lineno, values);
+
+            } else if (modelType == CSVFormat::WaveFileModel) {
+
+                int channel = 0;
+
+                for (int i = 0;
+                     i < list.size() && channel < audioChannels;
+                     ++i) {
+
+                    if (m_format.getColumnPurpose(i) !=
+                        CSVFormat::ColumnValue) {
+                        continue;
+                    }
+
+                    bool ok = false;
+                    float value = list[i].toFloat(&ok);
+                    if (!ok) {
+                        value = 0.f;
+                    }
+
+                    value += sampleShift;
+                    value *= sampleScale;
+                    
+                    audioSamples[channel][0] = value;
+
+                    ++channel;
+                }
+
+                while (channel < audioChannels) {
+                    audioSamples[channel][0] = 0.f;
+                    ++channel;
+                }
+
+                bool ok = modelW->addSamples(audioSamples, 1);
+                
+                if (!ok) {
+                    if (warnings < warnLimit) {
+                        SVCERR << "WARNING: CSVFileReader::load: "
+                               << "Unable to add sample to wave-file model"
+                               << endl;
+                        SVCERR << line << endl;
+                        ++warnings;
+                    }
+                }
             }
-
+            
             ++lineno;
             if (timingType == CSVFormat::ImplicitTiming ||
                 list.size() == 0) {
@@ -426,11 +572,11 @@
             for (map<int, map<QString, float> >::iterator i =
                      countLabelValueMap.end(); i != countLabelValueMap.begin(); ) {
                 --i;
-                cerr << "count -> " << i->first << endl;
+                SVCERR << "count -> " << i->first << endl;
                 for (map<QString, float>::iterator j = i->second.begin();
                      j != i->second.end(); ++j) {
                     j->second = v;
-                    cerr << "label -> " << j->first << ", value " << v << endl;
+                    SVCERR << "label -> " << j->first << ", value " << v << endl;
                     v = v + 1.f;
                 }
             }
@@ -443,7 +589,7 @@
                 RegionModel::Point p(*i);
                 int count = labelCountMap[p.label];
                 v = countLabelValueMap[count][p.label];
-                cerr << "mapping from label \"" << p.label << "\" (count " << count << ") to value " << v << endl;
+              //  SVCERR << "mapping from label \"" << p.label << "\" (count " << count << ") to value " << v << endl;
                 RegionModel::Point pp(p.frame, v, p.duration, p.label);
                 pointMap[p] = pp;
             }
@@ -474,10 +620,36 @@
     }
 
     if (model3) {
-	model3->setMinimumLevel(min);
-	model3->setMaximumLevel(max);
+        model3->setMinimumLevel(min);
+        model3->setMaximumLevel(max);
+    }
+
+    if (modelW) {
+        breakfastquay::deallocate_channels(audioSamples, audioChannels);
+        modelW->updateModel();
+        modelW->writeComplete();
     }
 
     return model;
 }
 
+QString
+CSVFileReader::getConvertedAudioFilePath() const
+{
+    QString base = m_filename;
+    base.replace(QRegExp("[/\\,.:;~<>\"'|?%*]+"), "_");
+
+    QString convertedFileDir = RecordDirectory::getConvertedAudioDirectory();
+    if (convertedFileDir == "") {
+        SVCERR << "WARNING: CSVFileReader::getConvertedAudioFilePath: Failed to retrieve converted audio directory" << endl;
+        return "";
+    }
+
+    auto ms = QDateTime::currentDateTime().toMSecsSinceEpoch();
+    auto s = ms / 1000; // there is a toSecsSinceEpoch in Qt 5.8 but
+                        // we currently want to support older versions
+    
+    return QDir(convertedFileDir).filePath
+        (QString("%1-%2.wav").arg(base).arg(s));
+}
+
--- a/data/fileio/CSVFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _CSV_FILE_READER_H_
-#define _CSV_FILE_READER_H_
+#ifndef SV_CSV_FILE_READER_H
+#define SV_CSV_FILE_READER_H
 
 #include "DataFileReader.h"
 
@@ -27,6 +27,7 @@
 #include <QIODevice>
 
 class QFile;
+class ProgressReporter;
 
 class CSVFileReader : public DataFileReader
 {
@@ -35,7 +36,9 @@
      * 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);
+    CSVFileReader(QString path, CSVFormat format,
+                  sv_samplerate_t mainModelSampleRate,
+                  ProgressReporter *reporter = 0);
 
     /**
      * Construct a CSVFileReader to read from the given
@@ -43,7 +46,9 @@
      * CSVFileReader will not close or delete it and it must outlive
      * the CSVFileReader.
      */
-    CSVFileReader(QIODevice *device, CSVFormat format, sv_samplerate_t mainModelSampleRate);
+    CSVFileReader(QIODevice *device, CSVFormat format,
+                  sv_samplerate_t mainModelSampleRate,
+                  ProgressReporter *reporter = 0);
 
     virtual ~CSVFileReader();
 
@@ -60,9 +65,15 @@
     QString m_error;
     mutable int m_warnings;
     sv_samplerate_t m_mainModelSampleRate;
+    qint64 m_fileSize;
+    mutable qint64 m_readCount;
+    mutable int m_progress;
+    ProgressReporter *m_reporter;
 
     sv_frame_t convertTimeValue(QString, int lineno, sv_samplerate_t sampleRate,
                                 int windowSize) const;
+
+    QString getConvertedAudioFilePath() const;
 };
 
 
--- a/data/fileio/CSVFileWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFileWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -14,6 +14,7 @@
 */
 
 #include "CSVFileWriter.h"
+#include "CSVStreamWriter.h"
 
 #include "model/Model.h"
 #include "model/SparseOneDimensionalModel.h"
@@ -27,6 +28,7 @@
 
 #include <QFile>
 #include <QTextStream>
+#include <exception>
 
 CSVFileWriter::CSVFileWriter(QString path,
                              Model *model,
@@ -59,30 +61,17 @@
 void
 CSVFileWriter::write()
 {
-    try {
-        TempWriteFile temp(m_path);
-
-        QFile file(temp.getTemporaryFilename());
-        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
-            m_error = tr("Failed to open file %1 for writing")
-                .arg(temp.getTemporaryFilename());
-            return;
-        }
-    
-        QTextStream out(&file);
-        out << m_model->toDelimitedDataStringWithOptions
-            (m_delimiter, m_options);
-
-        file.close();
-        temp.moveToTarget();
-
-    } catch (FileOperationFailed &f) {
-        m_error = f.what();
-    }
+    Selection all {
+        m_model->getStartFrame(),
+        m_model->getEndFrame()
+    };
+    MultiSelection selections;
+    selections.addSelection(all);
+    writeSelection(selections); 
 }
 
 void
-CSVFileWriter::writeSelection(MultiSelection *selection)
+CSVFileWriter::writeSelection(MultiSelection selection)
 {
     try {
         TempWriteFile temp(m_path);
@@ -96,22 +85,34 @@
     
         QTextStream out(&file);
 
-        for (MultiSelection::SelectionList::iterator i =
-                 selection->getSelections().begin();
-             i != selection->getSelections().end(); ++i) {
-	
-            sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
-            out << m_model->toDelimitedDataStringSubsetWithOptions
-                (m_delimiter, m_options, f0, f1);
+        sv_frame_t blockSize = 65536;
+
+        if (m_model->isSparse()) {
+            // Write the whole in one go, as re-seeking for each block
+            // may be very costly otherwise
+            sv_frame_t startFrame, endFrame;
+            selection.getExtents(startFrame, endFrame);
+            blockSize = endFrame - startFrame;
         }
+        
+        bool completed = CSVStreamWriter::writeInChunks(
+            out,
+            *m_model,
+            selection,
+            m_reporter,
+            m_delimiter,
+            m_options,
+            blockSize
+        );
 
         file.close();
-        temp.moveToTarget();
+        if (completed) {
+            temp.moveToTarget();
+        }
 
     } catch (FileOperationFailed &f) {
         m_error = f.what();
+    } catch (const std::exception &e) { // ProgressReporter could throw
+        m_error = e.what();
     }
 }
-
-
-
--- a/data/fileio/CSVFileWriter.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFileWriter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _CSV_FILE_WRITER_H_
-#define _CSV_FILE_WRITER_H_
+#ifndef SV_CSV_FILE_WRITER_H
+#define SV_CSV_FILE_WRITER_H
 
 #include <QObject>
 #include <QString>
@@ -23,6 +23,7 @@
 
 class Model;
 class MultiSelection;
+class ProgressReporter;
 
 class CSVFileWriter : public QObject
 {
@@ -33,13 +34,23 @@
                   Model *model,
                   QString delimiter = ",",
                   DataExportOptions options = DataExportDefaults);
+
+    CSVFileWriter(QString path,
+                  Model *model,
+                  ProgressReporter *reporter,
+                  QString delimiter = ",",
+                  DataExportOptions options = DataExportDefaults) 
+    : CSVFileWriter(path, model, delimiter, options)
+    {
+        m_reporter = reporter;
+    }
     virtual ~CSVFileWriter();
 
     virtual bool isOK() const;
     virtual QString getError() const;
 
     virtual void write();
-    virtual void writeSelection(MultiSelection *selection);
+    virtual void writeSelection(MultiSelection selection);
 
 protected:
     QString m_path;
@@ -47,6 +58,7 @@
     QString m_error;
     QString m_delimiter;
     DataExportOptions m_options;
+    ProgressReporter *m_reporter = nullptr;
 };
 
 #endif
--- a/data/fileio/CSVFormat.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFormat.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -25,18 +25,22 @@
 
 #include <iostream>
 
+#include "base/Debug.h"
+
 CSVFormat::CSVFormat(QString path) :
     m_separator(""),
     m_sampleRate(44100),
     m_windowSize(1024),
     m_allowQuoting(true)
 {
-    guessFormatFor(path);
+    (void)guessFormatFor(path);
 }
 
-void
+bool
 CSVFormat::guessFormatFor(QString path)
 {
+    m_separator = ""; // to prompt guessing for it
+
     m_modelType = TwoDimensionalModel;
     m_timingType = ExplicitTiming;
     m_timeUnits = TimeSeconds;
@@ -51,8 +55,17 @@
     m_prevValues.clear();
 
     QFile file(path);
-    if (!file.exists()) return;
-    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
+    if (!file.exists()) {
+        SVCERR << "CSVFormat::guessFormatFor(" << path
+               << "): File does not exist" << endl;
+        return false;
+    }
+    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+        SVCERR << "CSVFormat::guessFormatFor(" << path
+               << "): File could not be opened for reading" << endl;
+        return false;
+    }
+    SVDEBUG << "CSVFormat::guessFormatFor(" << path << ")" << endl;
 
     QTextStream in(&file);
     in.seek(0);
@@ -69,38 +82,52 @@
         for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
-            if (line.startsWith("#") || line == "") continue;
+            if (line.startsWith("#") || line == "") {
+                continue;
+            }
 
             guessQualities(line, lineno);
 
             ++lineno;
         }
 
-        if (lineno >= 50) break;
+        if (lineno >= 150) break;
     }
 
     guessPurposes();
+    guessAudioSampleRange();
+
+    return true;
 }
 
 void
 CSVFormat::guessSeparator(QString line)
 {
-    char candidates[] = { ',', '\t', ' ', '|', '/', ':' };
-    for (int i = 0; i < int(sizeof(candidates)/sizeof(candidates[0])); ++i) {
-        if (StringBits::split(line, candidates[i], m_allowQuoting).size() >= 2) {
+    QString candidates = "\t|,/: ";
+
+    for (int i = 0; i < candidates.length(); ++i) {
+        auto bits = StringBits::split(line, candidates[i], m_allowQuoting);
+        if (bits.size() >= 2) {
+            SVDEBUG << "Successfully split the line into:" << endl;
+            for (auto b: bits) {
+                SVDEBUG << b << endl;
+            }
             m_separator = candidates[i];
+            SVDEBUG << "Estimated column separator: '" << m_separator
+                    << "'" << endl;
             return;
         }
     }
-    m_separator = " ";
 }
 
 void
 CSVFormat::guessQualities(QString line, int lineno)
 {
-    if (m_separator == "") guessSeparator(line);
+    if (m_separator == "") {
+        guessSeparator(line);
+    }
 
-    QStringList list = StringBits::split(line, m_separator[0], m_allowQuoting);
+    QStringList list = StringBits::split(line, getSeparator(), m_allowQuoting);
 
     int cols = list.size();
     if (lineno == 0 || (cols > m_columnCount)) m_columnCount = cols;
@@ -110,10 +137,11 @@
     // something that indicates otherwise:
 
     ColumnQualities defaultQualities =
-        ColumnNumeric | ColumnIntegral | ColumnIncreasing | ColumnNearEmpty;
+        ColumnNumeric | ColumnIntegral | ColumnSmall |
+        ColumnIncreasing | ColumnNearEmpty;
     
     for (int i = 0; i < cols; ++i) {
-	    
+            
         while (m_columnQualities.size() <= i) {
             m_columnQualities.push_back(defaultQualities);
             m_prevValues.push_back(0.f);
@@ -124,10 +152,15 @@
 
         ColumnQualities qualities = m_columnQualities[i];
 
+// Looks like this is defined on Windows
+#undef small
+        
         bool numeric    = (qualities & ColumnNumeric);
         bool integral   = (qualities & ColumnIntegral);
         bool increasing = (qualities & ColumnIncreasing);
+        bool small      = (qualities & ColumnSmall);
         bool large      = (qualities & ColumnLarge); // this one defaults to off
+        bool signd      = (qualities & ColumnSigned); // also defaults to off
         bool emptyish   = (qualities & ColumnNearEmpty);
 
         if (lineno > 1 && s.trimmed() != "") {
@@ -144,9 +177,25 @@
                 value = (float)StringBits::stringToDoubleLocaleFree(s, &ok);
             }
             if (ok) {
-                if (lineno < 2 && value > 1000.f) large = true;
+                if (lineno < 2 && value > 1000.f) {
+                    large = true;
+                }
+                if (value < 0.f) {
+                    signd = true;
+                }
+                if (value < -1.f || value > 1.f) {
+                    small = false;
+                }
             } else {
                 numeric = false;
+
+                // If the column is not numeric, it can't be any of
+                // these things either
+                integral = false;
+                increasing = false;
+                small = false;
+                large = false;
+                signd = false;
             }
         }
 
@@ -166,12 +215,14 @@
 
             m_prevValues[i] = value;
         }
-
+        
         m_columnQualities[i] =
             (numeric    ? ColumnNumeric : 0) |
             (integral   ? ColumnIntegral : 0) |
             (increasing ? ColumnIncreasing : 0) |
+            (small      ? ColumnSmall : 0) |
             (large      ? ColumnLarge : 0) |
+            (signd      ? ColumnSigned : 0) |
             (emptyish   ? ColumnNearEmpty : 0);
     }
 
@@ -182,11 +233,13 @@
         }
     }
 
-//    cerr << "Estimated column qualities: ";
-//    for (int i = 0; i < m_columnCount; ++i) {
-//        cerr << int(m_columnQualities[i]) << " ";
-//    }
-//    cerr << endl;
+    if (lineno < 10) {
+        SVDEBUG << "Estimated column qualities for line " << lineno << " (reporting up to first 10): ";
+        for (int i = 0; i < m_columnCount; ++i) {
+            SVDEBUG << int(m_columnQualities[i]) << " ";
+        }
+        SVDEBUG << endl;
+    }
 }
 
 void
@@ -194,8 +247,15 @@
 {
     m_timingType = CSVFormat::ImplicitTiming;
     m_timeUnits = CSVFormat::TimeWindows;
-	
+        
     int timingColumnCount = 0;
+    bool haveDurationOrEndTime = false;
+
+    SVDEBUG << "Estimated column qualities overall: ";
+    for (int i = 0; i < m_columnCount; ++i) {
+        SVDEBUG << int(m_columnQualities[i]) << " ";
+    }
+    SVDEBUG << endl;
 
     // 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
@@ -251,6 +311,7 @@
 
                 if (timingColumnCount == 2 && m_timingType == ExplicitTiming) {
                     purpose = ColumnEndTime;
+                    haveDurationOrEndTime = true;
                 }
             }
         }
@@ -294,15 +355,17 @@
                 if (m_columnQualities[timecol] & ColumnIncreasing) {
                     // This shouldn't happen; should have been settled above
                     m_columnPurposes[timecol] = ColumnEndTime;
+                    haveDurationOrEndTime = true;
                 } else {
                     m_columnPurposes[timecol] = ColumnDuration;
+                    haveDurationOrEndTime = true;
                 }
                 --valueCount;
             }
         }
     }
 
-    if (timingColumnCount > 1) {
+    if (timingColumnCount > 1 || haveDurationOrEndTime) {
         m_modelType = TwoDimensionalModelWithDuration;
     } else {
         if (valueCount == 0) {
@@ -314,15 +377,83 @@
         }
     }
 
-//    cerr << "Estimated column purposes: ";
-//    for (int i = 0; i < m_columnCount; ++i) {
-//        cerr << int(m_columnPurposes[i]) << " ";
-//    }
-//    cerr << endl;
+    SVDEBUG << "Estimated column purposes: ";
+    for (int i = 0; i < m_columnCount; ++i) {
+        SVDEBUG << int(m_columnPurposes[i]) << " ";
+    }
+    SVDEBUG << endl;
 
-//    cerr << "Estimated model type: " << m_modelType << endl;
-//    cerr << "Estimated timing type: " << m_timingType << endl;
-//    cerr << "Estimated units: " << m_timeUnits << endl;
+    SVDEBUG << "Estimated model type: " << m_modelType << endl;
+    SVDEBUG << "Estimated timing type: " << m_timingType << endl;
+    SVDEBUG << "Estimated units: " << m_timeUnits << endl;
+}
+
+void
+CSVFormat::guessAudioSampleRange()
+{
+    AudioSampleRange range = SampleRangeSigned1;
+    
+    range = SampleRangeSigned1;
+    bool knownSigned = false;
+    bool knownNonIntegral = false;
+
+    SVDEBUG << "CSVFormat::guessAudioSampleRange: starting with assumption of "
+            << range << endl;
+    
+    for (int i = 0; i < m_columnCount; ++i) {
+        if (m_columnPurposes[i] != ColumnValue) {
+            SVDEBUG << "... column " << i
+                    << " is not apparently a value, ignoring" << endl;
+            continue;
+        }
+        if (!(m_columnQualities[i] & ColumnIntegral)) {
+            knownNonIntegral = true;
+            if (range == SampleRangeUnsigned255 ||
+                range == SampleRangeSigned32767) {
+                range = SampleRangeOther;
+            }
+            SVDEBUG << "... column " << i
+                    << " is non-integral, updating range to " << range << endl;
+        }
+        if (m_columnQualities[i] & ColumnLarge) {
+            if (range == SampleRangeSigned1 ||
+                range == SampleRangeUnsigned255) {
+                if (knownNonIntegral) {
+                    range = SampleRangeOther;
+                } else {
+                    range = SampleRangeSigned32767;
+                }
+            }
+            SVDEBUG << "... column " << i << " is large, updating range to "
+                    << range << endl;
+        }
+        if (m_columnQualities[i] & ColumnSigned) {
+            knownSigned = true;
+            if (range == SampleRangeUnsigned255) {
+                range = SampleRangeSigned32767;
+            }
+            SVDEBUG << "... column " << i << " is signed, updating range to "
+                    << range << endl;
+        }
+        if (!(m_columnQualities[i] & ColumnSmall)) {
+            if (range == SampleRangeSigned1) {
+                if (knownNonIntegral) {
+                    range = SampleRangeOther;
+                } else if (knownSigned) {
+                    range = SampleRangeSigned32767;
+                } else {
+                    range = SampleRangeUnsigned255;
+                }
+            }
+            SVDEBUG << "... column " << i << " is not small, updating range to "
+                    << range << endl;
+        }
+    }
+
+    SVDEBUG << "CSVFormat::guessAudioSampleRange: ended up with range "
+            << range << endl;
+    
+    m_audioSampleRange = range;
 }
 
 CSVFormat::ColumnPurpose
--- a/data/fileio/CSVFormat.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CSVFormat.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _CSV_FORMAT_H_
-#define _CSV_FORMAT_H_
+#ifndef SV_CSV_FORMAT_H
+#define SV_CSV_FORMAT_H
 
 #include <QString>
 #include <QStringList>
@@ -25,23 +25,24 @@
 {
 public:
     enum ModelType {
-	OneDimensionalModel,
-	TwoDimensionalModel,
+        OneDimensionalModel,
+        TwoDimensionalModel,
         TwoDimensionalModelWithDuration,
         TwoDimensionalModelWithDurationAndPitch,
-	ThreeDimensionalModel
+        ThreeDimensionalModel,
+        WaveFileModel
     };
     
     enum TimingType {
-	ExplicitTiming,
-	ImplicitTiming
+        ExplicitTiming,
+        ImplicitTiming
     };
 
     enum TimeUnits {
-	TimeSeconds,
+        TimeSeconds,
         TimeMilliseconds,
-	TimeAudioFrames,
-	TimeWindows,
+        TimeAudioFrames,
+        TimeWindows,
     };
 
     enum ColumnPurpose {
@@ -55,14 +56,23 @@
     };
 
     enum ColumnQuality {
-        ColumnNumeric    = 1,
-        ColumnIntegral   = 2,
-        ColumnIncreasing = 4,
-        ColumnLarge      = 8,
-        ColumnNearEmpty  = 16,
+        ColumnNumeric    = 1,   // No non-numeric values were seen in sample
+        ColumnIntegral   = 2,   // All sampled values were integers
+        ColumnIncreasing = 4,   // Sampled values were monotonically increasing
+        ColumnSmall      = 8,   // All sampled values had magnitude < 1
+        ColumnLarge      = 16,  // Values "quickly" grew to over 1000
+        ColumnSigned     = 32,  // Some negative values were seen
+        ColumnNearEmpty  = 64,  // Nothing in this column beyond first row
     };
     typedef unsigned int ColumnQualities;
 
+    enum AudioSampleRange {
+        SampleRangeSigned1 = 0, //     -1 .. 1
+        SampleRangeUnsigned255, //      0 .. 255
+        SampleRangeSigned32767, // -32768 .. 32767
+        SampleRangeOther        // Other/unknown: Normalise on load
+    };
+
     CSVFormat() : // arbitrary defaults
         m_modelType(TwoDimensionalModel),
         m_timingType(ExplicitTiming),
@@ -72,6 +82,7 @@
         m_windowSize(1024),
         m_columnCount(0),
         m_variableColumnCount(false),
+        m_audioSampleRange(SampleRangeOther),
         m_allowQuoting(true),
         m_maxExampleCols(0)
     { }
@@ -84,8 +95,21 @@
      * string, the separator character will also be guessed; otherwise
      * the current separator will be used.  The other properties of
      * this object will be set according to guesses from the file.
+     *
+     * The properties that are guessed from the file contents are:
+     * separator, column count, variable-column-count flag, audio
+     * sample range, timing type, time units, column qualities, column
+     * purposes, and model type. The sample rate and window size
+     * cannot be guessed and will not be changed by this function.
+     * Note also that this function will never guess WaveFileModel for
+     * the model type.
+     *
+     * Return false if there is some fundamental error, e.g. the file
+     * could not be opened at all. Return true otherwise. Note that
+     * this function returns true even if the file doesn't appear to
+     * make much sense as a data format.
      */
-    void guessFormatFor(QString path);
+    bool guessFormatFor(QString path);
  
     ModelType    getModelType()     const { return m_modelType;     }
     TimingType   getTimingType()    const { return m_timingType;    }
@@ -93,6 +117,7 @@
     sv_samplerate_t getSampleRate() const { return m_sampleRate;    }
     int          getWindowSize()    const { return m_windowSize;    }
     int          getColumnCount()   const { return m_columnCount;   }
+    AudioSampleRange getAudioSampleRange() const { return m_audioSampleRange; }
     bool         getAllowQuoting()  const { return m_allowQuoting;  }
     QChar        getSeparator()     const { 
         if (m_separator == "") return ' ';
@@ -106,6 +131,7 @@
     void setSampleRate(sv_samplerate_t r) { m_sampleRate   = r; }
     void setWindowSize(int s)             { m_windowSize   = s; }
     void setColumnCount(int c)            { m_columnCount  = c; }
+    void setAudioSampleRange(AudioSampleRange r) { m_audioSampleRange = r; }
     void setAllowQuoting(bool q)          { m_allowQuoting = q; }
 
     QList<ColumnPurpose> getColumnPurposes() const { return m_columnPurposes; }
@@ -116,12 +142,17 @@
     void setColumnPurpose(int i, ColumnPurpose p);
     
     // read-only; only valid if format has been guessed:
-    QList<ColumnQualities> getColumnQualities() const { return m_columnQualities; }
+    const QList<ColumnQualities> &getColumnQualities() const {
+        return m_columnQualities;
+    }
 
     // read-only; only valid if format has been guessed:
-    QList<QStringList> getExample() const { return m_example; }
+    const QList<QStringList> &getExample() const {
+        return m_example;
+    }
+    
     int getMaxExampleCols() const { return m_maxExampleCols; }
-	
+        
 protected:
     ModelType    m_modelType;
     TimingType   m_timingType;
@@ -136,6 +167,8 @@
     QList<ColumnQualities> m_columnQualities;
     QList<ColumnPurpose> m_columnPurposes;
 
+    AudioSampleRange m_audioSampleRange;
+
     QList<float> m_prevValues;
 
     bool m_allowQuoting;
@@ -146,9 +179,7 @@
     void guessSeparator(QString line);
     void guessQualities(QString line, int lineno);
     void guessPurposes();
-
-    void guessFormatFor_Old(QString path);
- 
+    void guessAudioSampleRange();
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/CSVStreamWriter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,150 @@
+/* -*- 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 2017 Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_CSV_STREAM_WRITER_H
+#define SV_CSV_STREAM_WRITER_H
+
+#include "base/BaseTypes.h"
+#include "base/Selection.h"
+#include "base/ProgressReporter.h"
+#include "base/DataExportOptions.h"
+#include "data/model/Model.h"
+#include <QString>
+#include <algorithm>
+#include <numeric>
+
+namespace CSVStreamWriter
+{
+
+template <class OutStream>
+bool
+writeInChunks(OutStream& oss,
+              const Model& model,
+              const MultiSelection& regions,
+              ProgressReporter* reporter = nullptr,
+              QString delimiter = ",",
+              DataExportOptions options = DataExportDefaults,
+              const sv_frame_t blockSize = 16384)
+{
+    const auto selections = regions.getSelections();
+    if (blockSize <= 0 || selections.empty()) return false;
+
+    // TODO, some form of checking validity of selections?
+    const auto nFramesToWrite = std::accumulate(
+        selections.begin(),
+        selections.end(),
+        0,
+        [](sv_frame_t acc, const Selection& current) -> sv_frame_t {
+            return acc + (current.getEndFrame() - current.getStartFrame());
+        }
+    );
+    const auto finalFrameOfLastRegion = (*selections.crbegin()).getEndFrame();
+
+    const auto wasCancelled = [&reporter]() { 
+        return reporter && reporter->wasCancelled(); 
+    };
+
+    sv_frame_t nFramesWritten = 0;
+    int previousProgress = 0;
+
+    for (const auto& extents : selections) {
+        const auto startFrame = extents.getStartFrame();
+        const auto endFrame = extents.getEndFrame();
+        auto readPtr = startFrame;
+        while (readPtr < endFrame) {
+            if (wasCancelled()) return false;
+
+            const auto start = readPtr;
+            const auto end = std::min(start + blockSize, endFrame);
+            const auto data = model.toDelimitedDataStringSubsetWithOptions(
+                delimiter,
+                options,
+                start,
+                end
+            ).trimmed();
+
+            if ( data != "" ) {
+                oss << data << (end < finalFrameOfLastRegion ? "\n" : "");
+            }
+
+            nFramesWritten += end - start;
+            const int currentProgress =
+                int(100 * nFramesWritten / nFramesToWrite);
+            const bool hasIncreased = currentProgress > previousProgress;
+            if (hasIncreased) {
+                if (reporter) reporter->setProgress(currentProgress);
+                previousProgress = currentProgress;
+            }
+            readPtr = end;
+        }
+    }
+    return !wasCancelled(); // setProgress could process event loop
+}
+
+template <class OutStream>
+bool 
+writeInChunks(OutStream& oss,
+              const Model& model,
+              const Selection& extents,
+              ProgressReporter* reporter = nullptr,
+              QString delimiter = ",",
+              DataExportOptions options = DataExportDefaults,
+              const sv_frame_t blockSize = 16384)
+{
+    const auto startFrame = extents.isEmpty() ?
+        model.getStartFrame() : extents.getStartFrame();
+    const auto endFrame = extents.isEmpty() ?
+        model.getEndFrame() : extents.getEndFrame();
+    const auto hasValidExtents = startFrame >= 0 && endFrame > startFrame;
+    if (!hasValidExtents) return false;
+    Selection all {
+        startFrame,
+        endFrame
+    };
+    MultiSelection regions;
+    regions.addSelection(all);
+    return CSVStreamWriter::writeInChunks(
+        oss,
+        model,
+        regions,
+        reporter,
+        delimiter,
+        options,
+        blockSize
+    );
+}
+
+template <class OutStream>
+bool 
+writeInChunks(OutStream& oss,
+              const Model& model,
+              ProgressReporter* reporter = nullptr,
+              QString delimiter = ",",
+              DataExportOptions options = DataExportDefaults,
+              const sv_frame_t blockSize = 16384)
+{
+    const Selection empty;
+    return CSVStreamWriter::writeInChunks(
+        oss,
+        model,
+        empty,
+        reporter,
+        delimiter,
+        options,
+        blockSize
+    );
+}
+} // namespace CSVStreamWriter
+#endif
--- a/data/fileio/CachedFile.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CachedFile.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -157,7 +157,7 @@
                 SVDEBUG << "CachedFile::check: Retrieval succeeded" << endl;
                 updateLastRetrieval(true);
             } else {
-                cerr << "CachedFile::check: Retrieval failed, will try again later (using existing file for now)" << endl;
+                SVCERR << "CachedFile::check: Retrieval failed, will try again later (using existing file for now)" << endl;
             }                
         }
     } else {
@@ -168,7 +168,7 @@
             m_ok = true;
             updateLastRetrieval(true);
         } else {
-            cerr << "CachedFile::check: Retrieval failed, remaining in invalid state" << endl;
+            SVCERR << "CachedFile::check: Retrieval failed, remaining in invalid state" << endl;
             // again, we don't need to do anything here -- the last
             // retrieval timestamp is already invalid
         }
@@ -212,7 +212,7 @@
     QFile previous(m_localFilename);
     if (previous.exists()) {
         if (!previous.remove()) {
-            cerr << "CachedFile::retrieve: ERROR: Failed to remove previous copy of cached file at \"" << m_localFilename << "\"" << endl;
+            SVCERR << "CachedFile::retrieve: ERROR: Failed to remove previous copy of cached file at \"" << m_localFilename << "\"" << endl;
             return false;
         }
     }
@@ -222,7 +222,7 @@
     //!!! disk space left)
 
     if (!tempFile.copy(m_localFilename)) {
-        cerr << "CachedFile::retrieve: ERROR: Failed to copy newly retrieved file from \"" << tempName << "\" to \"" << m_localFilename << "\"" << endl;
+        SVCERR << "CachedFile::retrieve: ERROR: Failed to copy newly retrieved file from \"" << tempName << "\" to \"" << m_localFilename << "\"" << endl;
         return false;
     }
 
--- a/data/fileio/CodedAudioFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CodedAudioFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -136,7 +136,7 @@
     
     if (m_fileRate == 0) {
         SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
-        throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
+        throw FileOperationFailed("(coded file)", "sample rate unknown (bug in subclass implementation?)");
     }
     if (m_sampleRate == 0) {
         m_sampleRate = m_fileRate;
@@ -144,10 +144,13 @@
     }
     if (m_fileRate != m_sampleRate) {
         SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " <<  m_sampleRate << endl;
-        m_resampler = new breakfastquay::Resampler
-            (breakfastquay::Resampler::FastestTolerable,
-             m_channelCount,
-             int(m_cacheWriteBufferFrames));
+
+        breakfastquay::Resampler::Parameters params;
+        params.quality = breakfastquay::Resampler::FastestTolerable;
+        params.maxBufferSize = int(m_cacheWriteBufferFrames);
+        params.initialSampleRate = m_fileRate;
+        m_resampler = new breakfastquay::Resampler(params, m_channelCount);
+
         double ratio = m_sampleRate / m_fileRate;
         m_resampleBufferFrames = int(ceil(double(m_cacheWriteBufferFrames) *
                                           ratio + 1));
@@ -161,7 +164,7 @@
 
         try {
             QDir dir(TempDirectory::getInstance()->getPath());
-            m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
+            m_cacheFileName = dir.filePath(QString("decoded_%1.w64")
                                            .arg((intptr_t)this));
 
             SF_INFO fileInfo;
@@ -193,10 +196,15 @@
             // tests.)
             //
             // So: now we write floats.
-            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
-    
-            m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
-                                          SFM_WRITE, &fileInfo);
+            fileInfo.format = SF_FORMAT_W64 | SF_FORMAT_FLOAT;
+
+#ifdef Q_OS_WIN
+            m_cacheFileWritePtr = sf_wchar_open
+                ((LPCWSTR)m_cacheFileName.utf16(), SFM_WRITE, &fileInfo);
+#else
+            m_cacheFileWritePtr = sf_open
+                (m_cacheFileName.toLocal8Bit(), SFM_WRITE, &fileInfo);
+#endif
 
             if (m_cacheFileWritePtr) {
 
@@ -220,7 +228,7 @@
                 m_cacheMode = CacheInMemory;
             }
 
-        } catch (DirectoryCreationFailed f) {
+        } catch (const DirectoryCreationFailed &f) {
             SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
             m_cacheMode = CacheInMemory;
         }
@@ -289,7 +297,7 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples)
+CodedAudioFileReader::addSamplesToDecodeCache(const floatvec_t &samples)
 {
     QMutexLocker locker(&m_cacheMutex);
 
@@ -470,7 +478,14 @@
 
     case CacheInMemory:
         m_dataLock.lock();
-        m_data.insert(m_data.end(), buffer, buffer + count);
+        try {
+            m_data.insert(m_data.end(), buffer, buffer + count);
+        } catch (const std::bad_alloc &e) {
+            m_data.clear();
+            SVCERR << "CodedAudioFileReader: Caught bad_alloc when trying to add " << count << " elements to buffer" << endl;
+            m_dataLock.unlock();
+            throw e;
+        }
         m_dataLock.unlock();
         break;
     }
@@ -517,8 +532,12 @@
              ratio,
              true);
 
-        if (m_frameCount + out > sv_frame_t(double(m_fileFrameCount) * ratio)) {
-            out = sv_frame_t(double(m_fileFrameCount) * ratio) - m_frameCount;
+        SVDEBUG << "CodedAudioFileReader::pushBufferResampling: resampled padFrames to " << out << " frames" << endl;
+
+        sv_frame_t expected = sv_frame_t(round(double(m_fileFrameCount) * ratio));
+        if (m_frameCount + out > expected) {
+            out = expected - m_frameCount;
+            SVDEBUG << "CodedAudioFileReader::pushBufferResampling: clipping that to " << out << " to avoid producing more samples than desired" << endl;
         }
 
         pushBufferNonResampling(m_resampleBuffer, out);
@@ -526,7 +545,7 @@
     }
 }
 
-vector<float>
+floatvec_t
 CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
     // Lock is only required in CacheInMemory mode (the cache file
@@ -538,7 +557,7 @@
         return {};
     }
 
-    vector<float> frames;
+    floatvec_t frames;
     
     switch (m_cacheMode) {
 
@@ -564,7 +583,7 @@
         sv_frame_t n = sv_frame_t(m_data.size());
         if (ix0 > n) ix0 = n;
         if (ix1 > n) ix1 = n;
-        frames = vector<float>(m_data.begin() + ix0, m_data.begin() + ix1);
+        frames = floatvec_t(m_data.begin() + ix0, m_data.begin() + ix1);
         m_dataLock.unlock();
         break;
     }
--- a/data/fileio/CodedAudioFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CodedAudioFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,15 +13,21 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _CODED_AUDIO_FILE_READER_H_
-#define _CODED_AUDIO_FILE_READER_H_
+#ifndef SV_CODED_AUDIO_FILE_READER_H
+#define SV_CODED_AUDIO_FILE_READER_H
 
 #include "AudioFileReader.h"
 
-#include <sndfile.h>
 #include <QMutex>
 #include <QReadWriteLock>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
+#include <sndfile.h>
+
 class WavFileReader;
 class Serialiser;
 
@@ -46,7 +52,7 @@
         DecodeThreaded // decode in a background thread after construction
     };
 
-    virtual std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
+    virtual floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
 
     virtual sv_samplerate_t getNativeRate() const { return m_fileRate; }
 
@@ -71,7 +77,7 @@
     // may throw InsufficientDiscSpace:
     void addSamplesToDecodeCache(float **samples, sv_frame_t nframes);
     void addSamplesToDecodeCache(float *samplesInterleaved, sv_frame_t nframes);
-    void addSamplesToDecodeCache(const std::vector<float> &interleaved);
+    void addSamplesToDecodeCache(const floatvec_t &interleaved);
 
     // may throw InsufficientDiscSpace:
     void finishDecodeCache();
@@ -95,7 +101,7 @@
 protected:
     QMutex m_cacheMutex;
     CacheMode m_cacheMode;
-    std::vector<float> m_data;
+    floatvec_t m_data;
     mutable QMutex m_dataLock;
     bool m_initialised;
     Serialiser *m_serialiser;
--- a/data/fileio/CoreAudioFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/CoreAudioFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -114,14 +114,14 @@
     
     UInt32 propsize = sizeof(AudioStreamBasicDescription);
     m_d->err = ExtAudioFileGetProperty
-	(m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd);
+        (m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd);
     
     if (m_d->err) {
         m_error = "CoreAudioReadStream: Error in getting basic description: code " + codestr(m_d->err);
         ExtAudioFileDispose(m_d->file);
         return;
     }
-	
+        
     m_channelCount = m_d->asbd.mChannelsPerFrame;
     m_fileRate = m_d->asbd.mSampleRate;
 
@@ -137,9 +137,9 @@
     m_d->asbd.mBytesPerPacket = sizeof(float) * m_channelCount;
     m_d->asbd.mFramesPerPacket = 1;
     m_d->asbd.mReserved = 0;
-	
+        
     m_d->err = ExtAudioFileSetProperty
-	(m_d->file, kExtAudioFileProperty_ClientDataFormat, propsize, &m_d->asbd);
+        (m_d->file, kExtAudioFileProperty_ClientDataFormat, propsize, &m_d->asbd);
     
     if (m_d->err) {
         m_error = "CoreAudioReadStream: Error in setting client format: code " + codestr(m_d->err);
--- a/data/fileio/DataFileReaderFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/DataFileReaderFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -32,21 +32,28 @@
                                     bool csv,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
                                     CSVFormat format,
-                                    sv_samplerate_t mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate,
+                                    ProgressReporter *reporter)
 {
     QString err;
 
     DataFileReader *reader = 0;
 
     if (!csv) {
-        reader = new MIDIFileReader(path, acquirer, mainModelSampleRate);
+        reader = new MIDIFileReader(path,
+                                    acquirer,
+                                    mainModelSampleRate,
+                                    reporter);
         if (reader->isOK()) return reader;
         if (reader->getError() != "") err = reader->getError();
         delete reader;
     }
 
     if (csv) {
-        reader = new CSVFileReader(path, format, mainModelSampleRate);
+        reader = new CSVFileReader(path,
+                                   format,
+                                   mainModelSampleRate,
+                                   reporter);
         if (reader->isOK()) return reader;
         if (reader->getError() != "") err = reader->getError();
         delete reader;
@@ -58,14 +65,15 @@
 DataFileReader *
 DataFileReaderFactory::createReader(QString path,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
-                                    sv_samplerate_t mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate,
+                                    ProgressReporter *reporter)
 {
     DataFileReader *reader = createReader
-        (path, false, acquirer, CSVFormat(), mainModelSampleRate);
+        (path, false, acquirer, CSVFormat(), mainModelSampleRate, reporter);
     if (reader) return reader;
 
     reader = createReader
-        (path, true, acquirer, CSVFormat(path), mainModelSampleRate);
+        (path, true, acquirer, CSVFormat(path), mainModelSampleRate, reporter);
     if (reader) return reader;
 
     return 0;
@@ -74,11 +82,13 @@
 Model *
 DataFileReaderFactory::load(QString path,
                             MIDIFileImportPreferenceAcquirer *acquirer,
-                            sv_samplerate_t mainModelSampleRate)
+                            sv_samplerate_t mainModelSampleRate,
+                            ProgressReporter *reporter)
 {
     DataFileReader *reader = createReader(path,
                                           acquirer,
-                                          mainModelSampleRate);
+                                          mainModelSampleRate,
+                                          reporter);
     if (!reader) return NULL;
 
     try {
@@ -94,12 +104,14 @@
 Model *
 DataFileReaderFactory::loadNonCSV(QString path,
                                   MIDIFileImportPreferenceAcquirer *acquirer,
-                                  sv_samplerate_t mainModelSampleRate)
+                                  sv_samplerate_t mainModelSampleRate,
+                                  ProgressReporter *reporter)
 {
     DataFileReader *reader = createReader(path, false,
                                           acquirer,
                                           CSVFormat(),
-                                          mainModelSampleRate);
+                                          mainModelSampleRate,
+                                          reporter);
     if (!reader) return NULL;
 
     try {
@@ -114,10 +126,12 @@
 
 Model *
 DataFileReaderFactory::loadCSV(QString path, CSVFormat format,
-                               sv_samplerate_t mainModelSampleRate)
+                               sv_samplerate_t mainModelSampleRate,
+                               ProgressReporter *reporter)
 {
     DataFileReader *reader = createReader(path, true, 0, format,
-                                          mainModelSampleRate);
+                                          mainModelSampleRate,
+                                          reporter);
     if (!reader) return NULL;
 
     try {
--- a/data/fileio/DataFileReaderFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/DataFileReaderFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _DATA_FILE_READER_FACTORY_H_
-#define _DATA_FILE_READER_FACTORY_H_
+#ifndef SV_DATA_FILE_READER_FACTORY_H
+#define SV_DATA_FILE_READER_FACTORY_H
 
 #include <QString>
 
@@ -23,6 +23,7 @@
 
 class DataFileReader;
 class Model;
+class ProgressReporter;
 
 class DataFileReaderFactory
 {
@@ -48,7 +49,8 @@
      */
     static DataFileReader *createReader(QString path,
                                         MIDIFileImportPreferenceAcquirer *,
-					sv_samplerate_t mainModelSampleRate);
+                                        sv_samplerate_t mainModelSampleRate,
+                                        ProgressReporter *reporter = 0);
 
     /**
      * Read the given path, if a suitable reader is available.
@@ -60,7 +62,8 @@
      */
     static Model *load(QString path,
                        MIDIFileImportPreferenceAcquirer *acquirer,
-                       sv_samplerate_t mainModelSampleRate);
+                       sv_samplerate_t mainModelSampleRate,
+                       ProgressReporter *reporter = 0);
 
     /**
      * Read the given path, if a suitable reader is available.
@@ -69,7 +72,8 @@
      */
     static Model *loadNonCSV(QString path,
                              MIDIFileImportPreferenceAcquirer *acquirer,
-                             sv_samplerate_t mainModelSampleRate);
+                             sv_samplerate_t mainModelSampleRate,
+                             ProgressReporter *reporter = 0);
 
     /**
      * Read the given path using the CSV reader with the given format.
@@ -77,13 +81,15 @@
      */
     static Model *loadCSV(QString path,
                           CSVFormat format,
-                          sv_samplerate_t mainModelSampleRate);
+                          sv_samplerate_t mainModelSampleRate,
+                          ProgressReporter *reporter = 0);
 
 protected:
     static DataFileReader *createReader(QString path, bool csv,
                                         MIDIFileImportPreferenceAcquirer *,
                                         CSVFormat format,
-					sv_samplerate_t mainModelSampleRate);
+                                        sv_samplerate_t mainModelSampleRate,
+                                        ProgressReporter *reporter = 0);
 };
 
 #endif
--- a/data/fileio/DecodingWavFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/DecodingWavFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -71,7 +71,7 @@
         sv_frame_t blockSize = 16384;
         sv_frame_t total = m_original->getFrameCount();
 
-        vector<float> block;
+        floatvec_t block;
 
         for (sv_frame_t i = 0; i < total; i += blockSize) {
 
@@ -128,7 +128,7 @@
     sv_frame_t blockSize = 16384;
     sv_frame_t total = m_reader->m_original->getFrameCount();
     
-    vector<float> block;
+    floatvec_t block;
     
     for (sv_frame_t i = 0; i < total; i += blockSize) {
         
@@ -151,7 +151,7 @@
 } 
 
 void
-DecodingWavFileReader::addBlock(const vector<float> &frames)
+DecodingWavFileReader::addBlock(const floatvec_t &frames)
 {
     addSamplesToDecodeCache(frames);
 
--- a/data/fileio/DecodingWavFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/DecodingWavFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -64,7 +64,7 @@
     WavFileReader *m_original;
     ProgressReporter *m_reporter;
 
-    void addBlock(const std::vector<float> &frames);
+    void addBlock(const floatvec_t &frames);
     
     class DecodeThread : public Thread
     {
--- a/data/fileio/FileFinder.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/FileFinder.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _FILE_FINDER_H_
-#define _FILE_FINDER_H_
+#ifndef SV_FILE_FINDER_H
+#define SV_FILE_FINDER_H
 
 #include <QString>
 
@@ -28,6 +28,7 @@
         LayerFileNoMidi,
         SessionOrAudioFile,
         ImageFile,
+        SVGFile,
         AnyFile,
         CSVFile,
         LayerFileNonSV,
--- a/data/fileio/FileSource.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/FileSource.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -884,7 +884,7 @@
     QDir dir;
     try {
         dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
-    } catch (DirectoryCreationFailed f) {
+    } catch (const DirectoryCreationFailed &f) {
 #ifdef DEBUG_FILE_SOURCE
         cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << endl;
 #endif
--- a/data/fileio/MIDIFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MIDIFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -53,12 +53,13 @@
 
 using namespace MIDIConstants;
 
-//#define MIDI_SVDEBUG 1
+//#define MIDI_DEBUG 1
 
 
 MIDIFileReader::MIDIFileReader(QString path,
                                MIDIFileImportPreferenceAcquirer *acquirer,
-			       sv_samplerate_t mainModelSampleRate) :
+                               sv_samplerate_t mainModelSampleRate,
+                               ProgressReporter *) : // we don't actually report progress
     m_smpte(false),
     m_timingDivision(0),
     m_fps(0),
@@ -74,21 +75,21 @@
     m_acquirer(acquirer)
 {
     if (parseFile()) {
-	m_error = "";
+        m_error = "";
     }
 }
 
 MIDIFileReader::~MIDIFileReader()
 {
     for (MIDIComposition::iterator i = m_midiComposition.begin();
-	 i != m_midiComposition.end(); ++i) {
-	
-	for (MIDITrack::iterator j = i->second.begin();
-	     j != i->second.end(); ++j) {
-	    delete *j;
-	}
+         i != m_midiComposition.end(); ++i) {
+        
+        for (MIDITrack::iterator j = i->second.begin();
+             j != i->second.end(); ++j) {
+            delete *j;
+        }
 
-	i->second.clear();
+        i->second.clear();
     }
 
     m_midiComposition.clear();
@@ -110,7 +111,7 @@
 MIDIFileReader::midiBytesToLong(const string& bytes)
 {
     if (bytes.length() != 4) {
-	throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
+        throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
     }
 
     long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
@@ -125,7 +126,7 @@
 MIDIFileReader::midiBytesToInt(const string& bytes)
 {
     if (bytes.length() != 2) {
-	throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
+        throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
     }
 
     int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
@@ -142,7 +143,7 @@
 MIDIFileReader::getMIDIByte()
 {
     if (!m_midiFile) {
-	throw MIDIException(tr("getMIDIByte called but no MIDI file open"));
+        throw MIDIException(tr("getMIDIByte called but no MIDI file open"));
     }
 
     if (m_midiFile->eof()) {
@@ -155,8 +156,8 @@
 
     char byte;
     if (m_midiFile->read(&byte, 1)) {
-	--m_trackByteCount;
-	return (MIDIByte)byte;
+        --m_trackByteCount;
+        return (MIDIByte)byte;
     }
 
     throw MIDIException(tr("Attempt to read past MIDI file end"));
@@ -171,7 +172,7 @@
 MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
 {
     if (!m_midiFile) {
-	throw MIDIException(tr("getMIDIBytes called but no MIDI file open"));
+        throw MIDIException(tr("getMIDIBytes called but no MIDI file open"));
     }
 
     if (m_midiFile->eof()) {
@@ -212,27 +213,27 @@
 MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
 {
     if (!m_midiFile) {
-	throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open"));
+        throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open"));
     }
 
     long longRet = 0;
     MIDIByte midiByte;
 
     if (firstByte >= 0) {
-	midiByte = (MIDIByte)firstByte;
+        midiByte = (MIDIByte)firstByte;
     } else if (m_midiFile->eof()) {
-	return longRet;
+        return longRet;
     } else {
-	midiByte = getMIDIByte();
+        midiByte = getMIDIByte();
     }
 
     longRet = midiByte;
     if (midiByte & 0x80) {
-	longRet &= 0x7F;
-	do {
-	    midiByte = getMIDIByte();
-	    longRet = (longRet << 7) + (midiByte & 0x7F);
-	} while (!m_midiFile->eof() && (midiByte & 0x80));
+        longRet &= 0x7F;
+        do {
+            midiByte = getMIDIByte();
+            longRet = (longRet << 7) + (midiByte & 0x7F);
+        } while (!m_midiFile->eof() && (midiByte & 0x80));
     }
 
     return longRet;
@@ -246,7 +247,7 @@
 MIDIFileReader::skipToNextTrack()
 {
     if (!m_midiFile) {
-	throw MIDIException(tr("skipToNextTrack called but no MIDI file open"));
+        throw MIDIException(tr("skipToNextTrack called but no MIDI file open"));
     }
 
     string buffer, buffer2;
@@ -255,10 +256,10 @@
 
     while (!m_midiFile->eof() && (m_decrementCount == false)) {
         buffer = getMIDIBytes(4); 
-	if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
-	    m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
-	    m_decrementCount = true;
-	}
+        if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
+            m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
+            m_decrementCount = true;
+        }
     }
 
     if (m_trackByteCount == -1) { // we haven't found a track
@@ -284,77 +285,77 @@
 
     // Open the file
     m_midiFile = new ifstream(m_path.toLocal8Bit().data(),
-			      ios::in | ios::binary);
+                              ios::in | ios::binary);
 
     if (!*m_midiFile) {
-	m_error = "File not found or not readable.";
-	m_format = MIDI_FILE_BAD_FORMAT;
-	delete m_midiFile;
+        m_error = "File not found or not readable.";
+        m_format = MIDI_FILE_BAD_FORMAT;
+        delete m_midiFile;
         m_midiFile = 0;
-	return false;
+        return false;
     }
 
     bool retval = false;
 
     try {
 
-	// Set file size so we can count it off
-	//
-	m_midiFile->seekg(0, ios::end);
+        // Set file size so we can count it off
+        //
+        m_midiFile->seekg(0, ios::end);
         std::streamoff off = m_midiFile->tellg();
-	m_fileSize = 0;
+        m_fileSize = 0;
         if (off > 0) m_fileSize = off;
-	m_midiFile->seekg(0, ios::beg);
+        m_midiFile->seekg(0, ios::beg);
 
-	// Parse the MIDI header first.  The first 14 bytes of the file.
-	if (!parseHeader(getMIDIBytes(14))) {
-	    m_format = MIDI_FILE_BAD_FORMAT;
-	    m_error = "Not a MIDI file.";
-	    goto done;
-	}
+        // Parse the MIDI header first.  The first 14 bytes of the file.
+        if (!parseHeader(getMIDIBytes(14))) {
+            m_format = MIDI_FILE_BAD_FORMAT;
+            m_error = "Not a MIDI file.";
+            goto done;
+        }
 
-	unsigned int i = 0;
+        unsigned int i = 0;
 
-	for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
+        for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
 
 #ifdef MIDI_DEBUG
-	    SVDEBUG << "Parsing Track " << j << endl;
+            SVDEBUG << "Parsing Track " << j << endl;
 #endif
 
-	    if (!skipToNextTrack()) {
+            if (!skipToNextTrack()) {
 #ifdef MIDI_DEBUG
-		cerr << "Couldn't find Track " << j << endl;
+                SVDEBUG << "Couldn't find Track " << j << endl;
 #endif
-		m_error = "File corrupted or in non-standard format?";
-		m_format = MIDI_FILE_BAD_FORMAT;
-		goto done;
-	    }
+                m_error = "File corrupted or in non-standard format?";
+                m_format = MIDI_FILE_BAD_FORMAT;
+                goto done;
+            }
 
 #ifdef MIDI_DEBUG
-	    cerr << "Track has " << m_trackByteCount << " bytes" << endl;
+            SVDEBUG << "Track has " << m_trackByteCount << " bytes" << endl;
 #endif
 
-	    // Run through the events taking them into our internal
-	    // representation.
-	    if (!parseTrack(i)) {
+            // Run through the events taking them into our internal
+            // representation.
+            if (!parseTrack(i)) {
 #ifdef MIDI_DEBUG
-		cerr << "Track " << j << " parsing failed" << endl;
+                SVDEBUG << "Track " << j << " parsing failed" << endl;
 #endif
-		m_error = "File corrupted or in non-standard format?";
-		m_format = MIDI_FILE_BAD_FORMAT;
-		goto done;
-	    }
+                m_error = "File corrupted or in non-standard format?";
+                m_format = MIDI_FILE_BAD_FORMAT;
+                goto done;
+            }
 
-	    ++i; // j is the source track number, i the destination
-	}
-	
-	m_numberOfTracks = i;
-	retval = true;
+            ++i; // j is the source track number, i the destination
+        }
+        
+        m_numberOfTracks = i;
+        retval = true;
 
-    } catch (MIDIException e) {
+    } catch (const MIDIException &e) {
 
         SVDEBUG << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
-	m_error = e.what();
+        m_error = e.what();
     }
     
 done:
@@ -367,7 +368,7 @@
         // start.  The addTime method returns the sum of the current
         // MIDI Event delta time plus the argument.
 
-	unsigned long acc = 0;
+        unsigned long acc = 0;
 
         for (MIDITrack::iterator i = m_midiComposition[track].begin();
              i != m_midiComposition[track].end(); ++i) {
@@ -375,8 +376,8 @@
         }
 
         if (consolidateNoteOffEvents(track)) { // returns true if some notes exist
-	    m_loadableTracks.insert(track);
-	}
+            m_loadableTracks.insert(track);
+        }
     }
 
     for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
@@ -402,18 +403,18 @@
 
     if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
 #ifdef MIDI_DEBUG
-	SVDEBUG << "MIDIFileReader::parseHeader()"
-	     << "- file header not found or malformed"
-	     << endl;
+        SVDEBUG << "MIDIFileReader::parseHeader()"
+             << "- file header not found or malformed"
+             << endl;
 #endif
-	return false;
+        return false;
     }
 
     if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
 #ifdef MIDI_DEBUG
         SVDEBUG << "MIDIFileReader::parseHeader()"
-	     << " - header length incorrect"
-	     << endl;
+             << " - header length incorrect"
+             << endl;
 #endif
         return false;
     }
@@ -474,18 +475,18 @@
 
     while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
 
-	if (eventCode < 0x80) {
+        if (eventCode < 0x80) {
 #ifdef MIDI_DEBUG
-	    cerr << "WARNING: Invalid event code " << eventCode
-		 << " in MIDI file" << endl;
+            SVDEBUG << "WARNING: Invalid event code " << eventCode
+                 << " in MIDI file" << endl;
 #endif
-	    throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode)));
-	}
+            throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode)));
+        }
 
         deltaTime = getNumberFromMIDIBytes();
 
 #ifdef MIDI_DEBUG
-	cerr << "read delta time " << deltaTime << endl;
+        SVDEBUG << "read delta time " << deltaTime << endl;
 #endif
 
         // Get a single byte
@@ -493,72 +494,72 @@
 
         if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
 
-	    if (runningStatus < 0) {
-		throw MIDIException(tr("Running status used for first event in track"));
-	    }
+            if (runningStatus < 0) {
+                throw MIDIException(tr("Running status used for first event in track"));
+            }
 
-	    eventCode = (MIDIByte)runningStatus;
-	    data1 = midiByte;
+            eventCode = (MIDIByte)runningStatus;
+            data1 = midiByte;
 
 #ifdef MIDI_DEBUG
-	    SVDEBUG << "using running status (byte " << int(midiByte) << " found)" << endl;
+            SVDEBUG << "using running status (byte " << int(midiByte) << " found)" << endl;
 #endif
         } else {
 #ifdef MIDI_DEBUG
-	    cerr << "have new event code " << int(midiByte) << endl;
+            SVDEBUG << "have new event code " << int(midiByte) << endl;
 #endif
             eventCode = midiByte;
-	    data1 = getMIDIByte();
-	}
+            data1 = getMIDIByte();
+        }
 
         if (eventCode == MIDI_FILE_META_EVENT) {
 
-	    metaEventCode = data1;
+            metaEventCode = data1;
             messageLength = getNumberFromMIDIBytes();
 
 //#ifdef MIDI_DEBUG
-		cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
+                SVDEBUG << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
 //#endif
             metaMessage = getMIDIBytes(messageLength);
 
-	    long gap = accumulatedTime - trackTimeMap[metaTrack];
-	    accumulatedTime += deltaTime;
-	    deltaTime += gap;
-	    trackTimeMap[metaTrack] = accumulatedTime;
+            long gap = accumulatedTime - trackTimeMap[metaTrack];
+            accumulatedTime += deltaTime;
+            deltaTime += gap;
+            trackTimeMap[metaTrack] = accumulatedTime;
 
             MIDIEvent *e = new MIDIEvent(deltaTime,
                                          MIDI_FILE_META_EVENT,
                                          metaEventCode,
                                          metaMessage);
 
-	    m_midiComposition[metaTrack].push_back(e);
+            m_midiComposition[metaTrack].push_back(e);
 
-	    if (metaEventCode == MIDI_TRACK_NAME) {
-		m_trackNames[metaTrack] = metaMessage.c_str();
-	    }
+            if (metaEventCode == MIDI_TRACK_NAME) {
+                m_trackNames[metaTrack] = metaMessage.c_str();
+            }
 
         } else { // non-meta events
 
-	    runningStatus = eventCode;
+            runningStatus = eventCode;
 
             MIDIEvent *midiEvent;
 
-	    int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
-	    if (channelTrackMap[channel] == -1) {
-		if (!firstTrack) ++lastTrackNum;
-		else firstTrack = false;
-		channelTrackMap[channel] = lastTrackNum;
-	    }
+            int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
+            if (channelTrackMap[channel] == -1) {
+                if (!firstTrack) ++lastTrackNum;
+                else firstTrack = false;
+                channelTrackMap[channel] = lastTrackNum;
+            }
 
-	    unsigned int trackNum = channelTrackMap[channel];
-	    
-	    // accumulatedTime is abs time of last event on any track;
-	    // trackTimeMap[trackNum] is that of last event on this track
-	    
-	    long gap = accumulatedTime - trackTimeMap[trackNum];
-	    accumulatedTime += deltaTime;
-	    deltaTime += gap;
-	    trackTimeMap[trackNum] = accumulatedTime;
+            unsigned int trackNum = channelTrackMap[channel];
+            
+            // accumulatedTime is abs time of last event on any track;
+            // trackTimeMap[trackNum] is that of last event on this track
+            
+            long gap = accumulatedTime - trackTimeMap[trackNum];
+            accumulatedTime += deltaTime;
+            deltaTime += gap;
+            trackTimeMap[trackNum] = accumulatedTime;
 
             switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
 
@@ -572,17 +573,17 @@
                 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2);
 
                 /*
-		cerr << "MIDI event for channel " << channel << " (track "
-			  << trackNum << ")" << endl;
-		midiEvent->print();
+                SVDEBUG << "MIDI event for channel " << channel << " (track "
+                          << trackNum << ")" << endl;
+                midiEvent->print();
                           */
 
 
                 m_midiComposition[trackNum].push_back(midiEvent);
 
-		if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) {
-		    m_percussionTracks.insert(trackNum);
-		}
+                if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) {
+                    m_percussionTracks.insert(trackNum);
+                }
 
                 break;
 
@@ -605,7 +606,7 @@
                 messageLength = getNumberFromMIDIBytes(data1);
 
 #ifdef MIDI_DEBUG
-		cerr << "SysEx of " << messageLength << " bytes found" << endl;
+                SVDEBUG << "SysEx of " << messageLength << " bytes found" << endl;
 #endif
 
                 metaMessage= getMIDIBytes(messageLength);
@@ -644,10 +645,10 @@
     }
 
     if (lastTrackNum > metaTrack) {
-	for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) {
-	    m_trackNames[track] = QString("%1 <%2>")
-		.arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1);
-	}
+        for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) {
+            m_trackNames[track] = QString("%1 <%2>")
+                .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1);
+        }
     }
 
     return true;
@@ -664,18 +665,18 @@
     bool noteOffFound;
 
     for (MIDITrack::iterator i = m_midiComposition[track].begin();
-	 i != m_midiComposition[track].end(); i++) {
+         i != m_midiComposition[track].end(); i++) {
 
         if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) {
 
-	    notesOnTrack = true;
+            notesOnTrack = true;
             noteOffFound = false;
 
             for (MIDITrack::iterator j = i;
-		 j != m_midiComposition[track].end(); j++) {
+                 j != m_midiComposition[track].end(); j++) {
 
                 if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) &&
-		    ((*j)->getPitch() == (*i)->getPitch()) &&
+                    ((*j)->getPitch() == (*i)->getPitch()) &&
                     ((*j)->getMessageType() == MIDI_NOTE_OFF ||
                     ((*j)->getMessageType() == MIDI_NOTE_ON &&
                      (*j)->getVelocity() == 0x00))) {
@@ -694,10 +695,10 @@
             // Event duration to length of track
             //
             if (!noteOffFound) {
-		MIDITrack::iterator j = m_midiComposition[track].end();
-		--j;
+                MIDITrack::iterator j = m_midiComposition[track].end();
+                --j;
                 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
-	    }
+            }
         }
     }
 
@@ -709,27 +710,27 @@
 void
 MIDIFileReader::updateTempoMap(unsigned int track)
 {
-    cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << endl;
+    SVDEBUG << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << endl;
 
     for (MIDITrack::iterator i = m_midiComposition[track].begin();
-	 i != m_midiComposition[track].end(); ++i) {
+         i != m_midiComposition[track].end(); ++i) {
 
         if ((*i)->isMeta() &&
-	    (*i)->getMetaEventCode() == MIDI_SET_TEMPO) {
+            (*i)->getMetaEventCode() == MIDI_SET_TEMPO) {
 
-	    MIDIByte m0 = (*i)->getMetaMessage()[0];
-	    MIDIByte m1 = (*i)->getMetaMessage()[1];
-	    MIDIByte m2 = (*i)->getMetaMessage()[2];
-	    
-	    long tempo = (((m0 << 8) + m1) << 8) + m2;
+            MIDIByte m0 = (*i)->getMetaMessage()[0];
+            MIDIByte m1 = (*i)->getMetaMessage()[1];
+            MIDIByte m2 = (*i)->getMetaMessage()[2];
+            
+            long tempo = (((m0 << 8) + m1) << 8) + m2;
 
-	    cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << endl;
+            SVDEBUG << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << endl;
 
-	    if (tempo != 0) {
-		double qpm = 60000000.0 / double(tempo);
-		m_tempoMap[(*i)->getTime()] =
-		    TempoChange(RealTime::zeroTime, qpm);
-	    }
+            if (tempo != 0) {
+                double qpm = 60000000.0 / double(tempo);
+                m_tempoMap[(*i)->getTime()] =
+                    TempoChange(RealTime::zeroTime, qpm);
+            }
         }
     }
 }
@@ -744,19 +745,19 @@
     if (td == 0) td = 96;
 
     for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) {
-	
-	unsigned long mtime = i->first;
-	unsigned long melapsed = mtime - lastMIDITime;
-	double quarters = double(melapsed) / double(td);
-	double seconds = (60.0 * quarters) / tempo;
+        
+        unsigned long mtime = i->first;
+        unsigned long melapsed = mtime - lastMIDITime;
+        double quarters = double(melapsed) / double(td);
+        double seconds = (60.0 * quarters) / tempo;
 
-	RealTime t = lastRealTime + RealTime::fromSeconds(seconds);
+        RealTime t = lastRealTime + RealTime::fromSeconds(seconds);
 
-	i->second.first = t;
+        i->second.first = t;
 
-	lastRealTime = t;
-	lastMIDITime = mtime;
-	tempo = i->second.second;
+        lastRealTime = t;
+        lastMIDITime = mtime;
+        tempo = i->second.second;
     }
 }
 
@@ -769,10 +770,10 @@
 
     TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime);
     if (i != m_tempoMap.begin()) {
-	--i;
-	tempoMIDITime = i->first;
-	tempoRealTime = i->second.first;
-	tempo = i->second.second;
+        --i;
+        tempoMIDITime = i->first;
+        tempoRealTime = i->second.first;
+        tempo = i->second.second;
     }
 
     int td = m_timingDivision;
@@ -784,13 +785,13 @@
 
 /*
     SVDEBUG << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")"
-	      << endl;
+              << endl;
     SVDEBUG << "timing division = " << td << endl;
-    cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " ("
-	      << tempoRealTime << ")" << endl;
-    cerr << "quarters since then = " << quarters << endl;
-    cerr << "tempo = " << tempo << " quarters per minute" << endl;
-    cerr << "seconds since then = " << seconds << endl;
+    SVDEBUG << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " ("
+              << tempoRealTime << ")" << endl;
+    SVDEBUG << "quarters since then = " << quarters << endl;
+    SVDEBUG << "tempo = " << tempo << " quarters per minute" << endl;
+    SVDEBUG << "seconds since then = " << seconds << endl;
     SVDEBUG << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << endl;
 */
 
@@ -807,40 +808,40 @@
             m_acquirer->showError
                 (tr("MIDI file \"%1\" has no notes in any track").arg(m_path));
         }
-	return 0;
+        return 0;
     }
 
     std::set<unsigned int> tracksToLoad;
 
     if (m_loadableTracks.size() == 1) {
 
-	tracksToLoad.insert(*m_loadableTracks.begin());
+        tracksToLoad.insert(*m_loadableTracks.begin());
 
     } else {
 
         QStringList displayNames;
 
-	for (set<unsigned int>::iterator i = m_loadableTracks.begin();
-	     i != m_loadableTracks.end(); ++i) {
+        for (set<unsigned int>::iterator i = m_loadableTracks.begin();
+             i != m_loadableTracks.end(); ++i) {
 
-	    unsigned int trackNo = *i;
-	    QString label;
+            unsigned int trackNo = *i;
+            QString label;
 
-	    QString perc;
-	    if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) {
-		perc = tr(" - uses GM percussion channel");
-	    }
+            QString perc;
+            if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) {
+                perc = tr(" - uses GM percussion channel");
+            }
 
-	    if (m_trackNames.find(trackNo) != m_trackNames.end()) {
-		label = tr("Track %1 (%2)%3")
-		    .arg(trackNo).arg(m_trackNames.find(trackNo)->second)
-		    .arg(perc);
-	    } else {
-		label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc);
-	    }
+            if (m_trackNames.find(trackNo) != m_trackNames.end()) {
+                label = tr("Track %1 (%2)%3")
+                    .arg(trackNo).arg(m_trackNames.find(trackNo)->second)
+                    .arg(perc);
+            } else {
+                label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc);
+            }
 
             displayNames << label;
-	}
+        }
 
         QString singleTrack;
 
@@ -866,28 +867,28 @@
             for (set<unsigned int>::iterator i = m_loadableTracks.begin();
                  i != m_loadableTracks.end(); ++i) {
                 
-		if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
-		    m_percussionTracks.find(*i) == m_percussionTracks.end()) {
+                if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
+                    m_percussionTracks.find(*i) == m_percussionTracks.end()) {
                     
-		    tracksToLoad.insert(*i);
-		}
-	    }
+                    tracksToLoad.insert(*i);
+                }
+            }
 
-	} else {
-	    
-	    int j = 0;
+        } else {
+            
+            int j = 0;
 
-	    for (set<unsigned int>::iterator i = m_loadableTracks.begin();
-		 i != m_loadableTracks.end(); ++i) {
-		
-		if (singleTrack == displayNames[j]) {
-		    tracksToLoad.insert(*i);
-		    break;
-		}
-		
-		++j;
-	    }
-	}
+            for (set<unsigned int>::iterator i = m_loadableTracks.begin();
+                 i != m_loadableTracks.end(); ++i) {
+                
+                if (singleTrack == displayNames[j]) {
+                    tracksToLoad.insert(*i);
+                    break;
+                }
+                
+                ++j;
+            }
+        }
     }
 
     if (tracksToLoad.empty()) return 0;
@@ -896,18 +897,18 @@
     Model *model = 0;
 
     for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
-	 i != tracksToLoad.end(); ++i) {
+         i != tracksToLoad.end(); ++i) {
 
-	int minProgress = (100 * count) / n;
-	int progressAmount = 100 / n;
+        int minProgress = (100 * count) / n;
+        int progressAmount = 100 / n;
 
-	model = loadTrack(*i, model, minProgress, progressAmount);
+        model = loadTrack(*i, model, minProgress, progressAmount);
 
-	++count;
+        ++count;
     }
 
     if (dynamic_cast<NoteModel *>(model)) {
-	dynamic_cast<NoteModel *>(model)->setCompletion(100);
+        dynamic_cast<NoteModel *>(model)->setCompletion(100);
     }
 
     return model;
@@ -915,26 +916,26 @@
 
 Model *
 MIDIFileReader::loadTrack(unsigned int trackToLoad,
-			  Model *existingModel,
-			  int minProgress,
-			  int progressAmount) const
+                          Model *existingModel,
+                          int minProgress,
+                          int progressAmount) const
 {
     if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) {
-	return 0;
+        return 0;
     }
 
     NoteModel *model = 0;
 
     if (existingModel) {
-	model = dynamic_cast<NoteModel *>(existingModel);
-	if (!model) {
-	    cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << endl;
-	}
+        model = dynamic_cast<NoteModel *>(existingModel);
+        if (!model) {
+            SVDEBUG << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << endl;
+        }
     }
 
     if (!model) {
-	model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
-	model->setValueQuantization(1.0);
+        model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
+        model->setValueQuantization(1.0);
         model->setObjectName(QFileInfo(m_path).fileName());
     }
 
@@ -956,54 +957,54 @@
             rt = getTimeForMIDITime(midiTime);
         }
 
-	// We ignore most of these event types for now, though in
-	// theory some of the text ones could usefully be incorporated
+        // We ignore most of these event types for now, though in
+        // theory some of the text ones could usefully be incorporated
 
-	if ((*i)->isMeta()) {
+        if ((*i)->isMeta()) {
 
-	    switch((*i)->getMetaEventCode()) {
+            switch((*i)->getMetaEventCode()) {
 
-	    case MIDI_KEY_SIGNATURE:
-		// minorKey = (int((*i)->getMetaMessage()[1]) != 0);
-		sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
-		break;
+            case MIDI_KEY_SIGNATURE:
+                // minorKey = (int((*i)->getMetaMessage()[1]) != 0);
+                sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
+                break;
 
-	    case MIDI_TEXT_EVENT:
-	    case MIDI_LYRIC:
-	    case MIDI_TEXT_MARKER:
-	    case MIDI_COPYRIGHT_NOTICE:
-	    case MIDI_TRACK_NAME:
-		// The text events that we could potentially use
-		break;
+            case MIDI_TEXT_EVENT:
+            case MIDI_LYRIC:
+            case MIDI_TEXT_MARKER:
+            case MIDI_COPYRIGHT_NOTICE:
+            case MIDI_TRACK_NAME:
+                // The text events that we could potentially use
+                break;
 
-	    case MIDI_SET_TEMPO:
-		// Already dealt with in a separate pass previously
-		break;
+            case MIDI_SET_TEMPO:
+                // Already dealt with in a separate pass previously
+                break;
 
-	    case MIDI_TIME_SIGNATURE:
-		// Not yet!
-		break;
+            case MIDI_TIME_SIGNATURE:
+                // Not yet!
+                break;
 
-	    case MIDI_SEQUENCE_NUMBER:
-	    case MIDI_CHANNEL_PREFIX_OR_PORT:
-	    case MIDI_INSTRUMENT_NAME:
-	    case MIDI_CUE_POINT:
-	    case MIDI_CHANNEL_PREFIX:
-	    case MIDI_SEQUENCER_SPECIFIC:
-	    case MIDI_SMPTE_OFFSET:
-	    default:
-		break;
-	    }
+            case MIDI_SEQUENCE_NUMBER:
+            case MIDI_CHANNEL_PREFIX_OR_PORT:
+            case MIDI_INSTRUMENT_NAME:
+            case MIDI_CUE_POINT:
+            case MIDI_CHANNEL_PREFIX:
+            case MIDI_SEQUENCER_SPECIFIC:
+            case MIDI_SMPTE_OFFSET:
+            default:
+                break;
+            }
 
-	} else {
+        } else {
 
-	    switch ((*i)->getMessageType()) {
+            switch ((*i)->getMessageType()) {
 
-	    case MIDI_NOTE_ON:
+            case MIDI_NOTE_ON:
 
                 if ((*i)->getVelocity() == 0) break; // effective note-off
-		else {
-		    RealTime endRT;
+                else {
+                    RealTime endRT;
                     unsigned long endMidiTime = (*i)->getTime() + (*i)->getDuration();
                     if (m_smpte) {
                         endRT = RealTime::frame2RealTime(endMidiTime, m_fps * m_subframes);
@@ -1011,32 +1012,32 @@
                         endRT = getTimeForMIDITime(endMidiTime);
                     }
 
-		    long startFrame = RealTime::realTime2Frame
-			(rt, model->getSampleRate());
+                    long startFrame = RealTime::realTime2Frame
+                        (rt, model->getSampleRate());
 
-		    long endFrame = RealTime::realTime2Frame
-			(endRT, model->getSampleRate());
+                    long endFrame = RealTime::realTime2Frame
+                        (endRT, model->getSampleRate());
 
-		    QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(),
-							      0, 
-							      !sharpKey);
+                    QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(),
+                                                              0, 
+                                                              !sharpKey);
 
-		    QString noteLabel = tr("%1 - vel %2")
-			.arg(pitchLabel).arg(int((*i)->getVelocity()));
+                    QString noteLabel = tr("%1 - vel %2")
+                        .arg(pitchLabel).arg(int((*i)->getVelocity()));
 
                     float level = float((*i)->getVelocity()) / 128.f;
 
-		    Note note(startFrame, (*i)->getPitch(),
-			      endFrame - startFrame, level, noteLabel);
+                    Note note(startFrame, (*i)->getPitch(),
+                              endFrame - startFrame, level, noteLabel);
 
-//		    SVDEBUG << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << endl;
+//                    SVDEBUG << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << endl;
 
-		    model->addPoint(note);
-		    break;
-		}
+                    model->addPoint(note);
+                    break;
+                }
 
             case MIDI_PITCH_BEND:
-		// I guess we could make some use of this...
+                // I guess we could make some use of this...
                 break;
 
             case MIDI_NOTE_OFF:
@@ -1050,11 +1051,11 @@
             default:
                 break;
             }
-	}
+        }
 
-	model->setCompletion(minProgress +
-			     (count * progressAmount) / totalEvents);
-	++count;
+        model->setCompletion(minProgress +
+                             (count * progressAmount) / totalEvents);
+        ++count;
     }
 
     return model;
--- a/data/fileio/MIDIFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MIDIFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -12,15 +12,14 @@
     COPYING included with this distribution for more information.
 */
 
-
 /*
    This is a modified version of a source file from the 
    Rosegarden MIDI and audio sequencer and notation editor.
    This file copyright 2000-2006 Richard Bown and Chris Cannam.
 */
 
-#ifndef _MIDI_FILE_READER_H_
-#define _MIDI_FILE_READER_H_
+#ifndef SV_MIDI_FILE_READER_H
+#define SV_MIDI_FILE_READER_H
 
 #include "DataFileReader.h"
 #include "base/RealTime.h"
@@ -32,6 +31,7 @@
 #include <QObject>
 
 class MIDIEvent;
+class ProgressReporter;
 
 typedef unsigned char MIDIByte;
 
@@ -61,8 +61,9 @@
 
 public:
     MIDIFileReader(QString path,
-                   MIDIFileImportPreferenceAcquirer *pref,
-                   sv_samplerate_t mainModelSampleRate);
+                   MIDIFileImportPreferenceAcquirer *pref, // may be null
+                   sv_samplerate_t mainModelSampleRate,
+                   ProgressReporter *reporter = 0);
     virtual ~MIDIFileReader();
 
     virtual bool isOK() const;
@@ -76,10 +77,10 @@
     typedef std::map<unsigned long, TempoChange> TempoMap; // key is MIDI time
 
     typedef enum {
-	MIDI_SINGLE_TRACK_FILE          = 0x00,
-	MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
-	MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
-	MIDI_FILE_BAD_FORMAT            = 0xFF
+        MIDI_SINGLE_TRACK_FILE          = 0x00,
+        MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
+        MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
+        MIDI_FILE_BAD_FORMAT            = 0xFF
     } MIDIFileFormatType;
 
     bool parseFile();
@@ -87,9 +88,9 @@
     bool parseTrack(unsigned int &trackNum);
 
     Model *loadTrack(unsigned int trackNum,
-		     Model *existingModel = 0,
-		     int minProgress = 0,
-		     int progressAmount = 100) const;
+                     Model *existingModel = 0,
+                     int minProgress = 0,
+                     int progressAmount = 100) const;
 
     bool consolidateNoteOffEvents(unsigned int track);
     void updateTempoMap(unsigned int track);
--- a/data/fileio/MIDIFileWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MIDIFileWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -56,14 +56,14 @@
 MIDIFileWriter::~MIDIFileWriter()
 {
     for (MIDIComposition::iterator i = m_midiComposition.begin();
-	 i != m_midiComposition.end(); ++i) {
-	
-	for (MIDITrack::iterator j = i->second.begin();
-	     j != i->second.end(); ++j) {
-	    delete *j;
-	}
+         i != m_midiComposition.end(); ++i) {
+        
+        for (MIDITrack::iterator j = i->second.begin();
+             j != i->second.end(); ++j) {
+            delete *j;
+        }
 
-	i->second.clear();
+        i->second.clear();
     }
 
     m_midiComposition.clear();
--- a/data/fileio/MIDIFileWriter.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MIDIFileWriter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -60,10 +60,10 @@
     typedef std::map<unsigned int, MIDITrack> MIDIComposition;
 
     typedef enum {
-	MIDI_SINGLE_TRACK_FILE          = 0x00,
-	MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
-	MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
-	MIDI_FILE_BAD_FORMAT            = 0xFF
+        MIDI_SINGLE_TRACK_FILE          = 0x00,
+        MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
+        MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
+        MIDI_FILE_BAD_FORMAT            = 0xFF
     } MIDIFileFormatType;
 
     std::string intToMIDIBytes(int number) const;
--- a/data/fileio/MP3FileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MP3FileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -32,12 +32,17 @@
 #include <id3tag.h>
 #endif
 
+#ifdef _WIN32
+#include <io.h>
+#include <fcntl.h>
+#else
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
 #include <QFileInfo>
 
-#ifdef _MSC_VER
-#include <io.h>
-#define open _open
-#endif
+#include <QTextCodec>
 
 using std::string;
 
@@ -75,13 +80,7 @@
         CodedAudioFileReader::setFramesToTrim(DEFAULT_DECODER_DELAY, 0);
     }
     
-    struct stat stat;
-    if (::stat(m_path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) {
-	m_error = QString("File %1 does not exist.").arg(m_path);
-	return;
-    }
-
-    m_fileSize = stat.st_size;
+    m_fileSize = 0;
 
     m_fileBuffer = 0;
     m_fileBufferSize = 0;
@@ -89,53 +88,42 @@
     m_sampleBuffer = 0;
     m_sampleBufferSize = 0;
 
-    int fd = -1;
-    if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY
-#ifdef _WIN32
-                     | O_BINARY
-#endif
-                     , 0)) < 0) {
-	m_error = QString("Failed to open file %1 for reading.").arg(m_path);
-	return;
-    }	
+    QFile qfile(m_path);
+    if (!qfile.open(QIODevice::ReadOnly)) {
+        m_error = QString("Failed to open file %1 for reading.").arg(m_path);
+        SVDEBUG << "MP3FileReader: " << m_error << endl;
+        return;
+    }   
 
+    m_fileSize = qfile.size();
+    
     try {
         // We need a mysterious MAD_BUFFER_GUARD (== 8) zero bytes at
         // end of input, to ensure libmad decodes the last frame
         // correctly. Otherwise the decoded audio is truncated.
+        SVDEBUG << "file size = " << m_fileSize << ", buffer guard = " << MAD_BUFFER_GUARD << endl;
         m_fileBufferSize = m_fileSize + MAD_BUFFER_GUARD;
         m_fileBuffer = new unsigned char[m_fileBufferSize];
         memset(m_fileBuffer + m_fileSize, 0, MAD_BUFFER_GUARD);
     } catch (...) {
         m_error = QString("Out of memory");
-        ::close(fd);
-	return;
-    }
-    
-    ssize_t sz = 0;
-    ssize_t offset = 0;
-    while (offset < m_fileSize) {
-        sz = ::read(fd, m_fileBuffer + offset, m_fileSize - offset);
-        if (sz < 0) {
-            m_error = QString("Read error for file %1 (after %2 bytes)")
-                .arg(m_path).arg(offset);
-            delete[] m_fileBuffer;
-            ::close(fd);
-            return;
-        } else if (sz == 0) {
-            SVCERR << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes")
-                .arg(offset).arg(m_fileSize) << endl;
-            m_fileSize = offset;
-            m_fileBufferSize = m_fileSize + MAD_BUFFER_GUARD;
-            memset(m_fileBuffer + m_fileSize, 0, MAD_BUFFER_GUARD);
-            break;
-        }
-        offset += sz;
+        SVDEBUG << "MP3FileReader: " << m_error << endl;
+        return;
     }
 
-    ::close(fd);
+    auto amountRead = qfile.read(reinterpret_cast<char *>(m_fileBuffer),
+                                 m_fileSize);
 
-    loadTags();
+    if (amountRead < m_fileSize) {
+        SVCERR << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes")
+            .arg(amountRead).arg(m_fileSize) << endl;
+        memset(m_fileBuffer + amountRead, 0, m_fileSize - amountRead);
+        m_fileSize = amountRead;
+    }
+        
+    loadTags(qfile.handle());
+
+    qfile.close();
 
     if (decodeMode == DecodeAtOnce) {
 
@@ -148,6 +136,14 @@
         if (!decode(m_fileBuffer, m_fileBufferSize)) {
             m_error = QString("Failed to decode file %1.").arg(m_path);
         }
+
+        if (m_sampleBuffer) {
+            for (int c = 0; c < m_channelCount; ++c) {
+                delete[] m_sampleBuffer[c];
+            }
+            delete[] m_sampleBuffer;
+            m_sampleBuffer = 0;
+        }
         
         delete[] m_fileBuffer;
         m_fileBuffer = 0;
@@ -191,14 +187,19 @@
 }
 
 void
-MP3FileReader::loadTags()
+MP3FileReader::loadTags(int fd)
 {
     m_title = "";
 
 #ifdef HAVE_ID3TAG
 
-    id3_file *file = id3_file_open(m_path.toLocal8Bit().data(),
-                                   ID3_FILE_MODE_READONLY);
+#ifdef _WIN32
+    int id3fd = _dup(fd);
+#else
+    int id3fd = dup(fd);
+#endif
+
+    id3_file *file = id3_file_fdopen(id3fd, ID3_FILE_MODE_READONLY);
     if (!file) return;
 
     // We can do this a lot more elegantly, but we'll leave that for
@@ -207,7 +208,7 @@
     id3_tag *tag = id3_file_tag(file);
     if (!tag) {
         SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl;
-        id3_file_close(file);
+        id3_file_close(file); // also closes our dup'd fd
         return;
     }
 
@@ -228,7 +229,7 @@
         }
     }
 
-    id3_file_close(file);
+    id3_file_close(file); // also closes our dup'd fd
 
 #else
     SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in" << endl;
@@ -479,7 +480,7 @@
 
 enum mad_flow
 MP3FileReader::accept(struct mad_header const *header,
-		      struct mad_pcm *pcm)
+                      struct mad_pcm *pcm)
 {
     int channels = pcm->channels;
     int frames = pcm->length;
@@ -496,6 +497,10 @@
         m_fileRate = pcm->samplerate;
         m_channelCount = channels;
 
+        SVDEBUG << "MP3FileReader::accept: file rate = " << pcm->samplerate
+                << ", channel count = " << channels << ", about to init "
+                << "decode cache" << endl;
+
         initialiseDecodeCache();
 
         if (m_cacheMode == CacheInTemporaryFile) {
@@ -525,6 +530,9 @@
     }
 
     if (!isDecodeCacheInitialised()) {
+        SVDEBUG << "MP3FileReader::accept: fallback case: file rate = " << pcm->samplerate
+                << ", channel count = " << channels << ", about to init "
+                << "decode cache" << endl;
         initialiseDecodeCache();
     }
 
@@ -548,14 +556,14 @@
 
         for (int i = 0; i < frames; ++i) {
 
-	    mad_fixed_t sample = 0;
-	    if (ch < activeChannels) {
-		sample = pcm->samples[ch][i];
-	    }
-	    float fsample = float(sample) / float(MAD_F_ONE);
+            mad_fixed_t sample = 0;
+            if (ch < activeChannels) {
+                sample = pcm->samples[ch][i];
+            }
+            float fsample = float(sample) / float(MAD_F_ONE);
             
             m_sampleBuffer[ch][i] = fsample;
-	}
+        }
     }
 
     addSamplesToDecodeCache(m_sampleBuffer, frames);
@@ -584,8 +592,8 @@
     if (!data->reader->m_decodeErrorShown) {
         char buffer[256];
         snprintf(buffer, 255,
-                 "MP3 decoding error 0x%04x (%s) at byte offset %lu",
-                 stream->error, mad_stream_errorstr(stream), ix);
+                 "MP3 decoding error 0x%04x (%s) at byte offset %lld",
+                 stream->error, mad_stream_errorstr(stream), (long long int)ix);
         SVCERR << "Warning: in file \"" << data->reader->m_path << "\": "
                << buffer << " (continuing; will not report any further decode errors for this file)" << endl;
         data->reader->m_decodeErrorShown = true;
--- a/data/fileio/MP3FileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/MP3FileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -122,10 +122,10 @@
     bool m_decodeErrorShown;
 
     struct DecoderData {
-	unsigned char const *start;
-	sv_frame_t length;
+        unsigned char const *start;
+        sv_frame_t length;
         bool finished;
-	MP3FileReader *reader;
+        MP3FileReader *reader;
     };
 
     bool decode(void *mm, sv_frame_t sz);
@@ -152,7 +152,7 @@
 
     DecodeThread *m_decodeThread;
 
-    void loadTags();
+    void loadTags(int fd);
     QString loadTag(void *vtag, const char *name);
 };
 
--- a/data/fileio/OggVorbisFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/OggVorbisFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -40,6 +40,10 @@
     CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
+    m_qfile(0),
+    m_ffile(0),
+    m_oggz(0),
+    m_fishSound(0),
     m_reporter(reporter),
     m_fileSize(0),
     m_bytesRead(0),
@@ -60,12 +64,36 @@
 
     Profiler profiler("OggVorbisFileReader::OggVorbisFileReader");
 
-    QFileInfo info(m_path);
-    m_fileSize = info.size();
+    // These shenanigans are to avoid using oggz_open(..) with a local
+    // codepage on Windows (make sure proper filename encoding is used)
+    
+    m_qfile = new QFile(m_path);
+    if (!m_qfile->open(QIODevice::ReadOnly)) {
+        m_error = QString("Failed to open file %1 for reading.").arg(m_path);
+        SVDEBUG << "OggVorbisFileReader: " << m_error << endl;
+        delete m_qfile;
+        m_qfile = 0;
+        return;
+    }
+    
+    m_fileSize = m_qfile->size();
 
-    if (!(m_oggz = oggz_open(m_path.toLocal8Bit().data(), OGGZ_READ))) {
-	m_error = QString("File %1 is not an OGG file.").arg(m_path);
-	return;
+    m_ffile = fdopen(dup(m_qfile->handle()), "rb");
+    if (!m_ffile) {
+        m_error = QString("Failed to open file pointer for file %1").arg(m_path);
+        SVDEBUG << "OggVorbisFileReader: " << m_error << endl;
+        delete m_qfile;
+        m_qfile = 0;
+        return;
+    }
+    
+    if (!(m_oggz = oggz_open_stdio(m_ffile, OGGZ_READ))) {
+        m_error = QString("File %1 is not an OGG file.").arg(m_path);
+        fclose(m_ffile);
+        m_ffile = 0;
+        delete m_qfile;
+        m_qfile = 0;
+        return;
     }
 
     FishSoundInfo fsinfo;
@@ -114,6 +142,11 @@
         m_decodeThread->wait();
         delete m_decodeThread;
     }
+    if (m_qfile) {
+        // don't fclose m_ffile; oggz_close did that
+        delete m_qfile;
+        m_qfile = 0;
+    }
 }
 
 void
@@ -134,8 +167,14 @@
         
     fish_sound_delete(m_reader->m_fishSound);
     m_reader->m_fishSound = 0;
+
     oggz_close(m_reader->m_oggz);
     m_reader->m_oggz = 0;
+
+    // don't fclose m_ffile; oggz_close did that
+
+    delete m_reader->m_qfile;
+    m_reader->m_qfile = 0;
     
     if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
     m_reader->m_completion = 100;
@@ -172,7 +211,7 @@
 
 int
 OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes,
-				  void *data)
+                                  void *data)
 {
     OggVorbisFileReader *reader = (OggVorbisFileReader *)data;
 
@@ -196,11 +235,11 @@
     }
 
     if (reader->m_channelCount == 0) {
-	FishSoundInfo fsinfo;
-	fish_sound_command(fs, FISH_SOUND_GET_INFO,
-			   &fsinfo, sizeof(FishSoundInfo));
-	reader->m_fileRate = fsinfo.samplerate;
-	reader->m_channelCount = fsinfo.channels;
+        FishSoundInfo fsinfo;
+        fish_sound_command(fs, FISH_SOUND_GET_INFO,
+                           &fsinfo, sizeof(FishSoundInfo));
+        reader->m_fileRate = fsinfo.samplerate;
+        reader->m_channelCount = fsinfo.channels;
         reader->initialiseDecodeCache();
     }
 
--- a/data/fileio/OggVorbisFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/OggVorbisFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _OGG_VORBIS_FILE_READER_H_
-#define _OGG_VORBIS_FILE_READER_H_
+#ifndef SV_OGG_VORBIS_FILE_READER_H
+#define SV_OGG_VORBIS_FILE_READER_H
 
 #ifdef HAVE_OGGZ
 #ifdef HAVE_FISHSOUND
@@ -25,6 +25,8 @@
 #include <oggz/oggz.h>
 #include <fishsound/fishsound.h>
 
+#include <cstdio>
+
 #include <set>
 
 class ProgressReporter;
@@ -71,6 +73,8 @@
     QString m_maker;
     TagMap m_tags;
 
+    QFile *m_qfile;
+    FILE *m_ffile;
     OGGZ *m_oggz;
     FishSound *m_fishSound;
     ProgressReporter *m_reporter;
--- a/data/fileio/PlaylistFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/PlaylistFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -66,13 +66,13 @@
     bool good = false;
 
     if (!m_file->exists()) {
-	m_error = QFile::tr("File \"%1\" does not exist")
+        m_error = QFile::tr("File \"%1\" does not exist")
             .arg(m_source.getLocation());
     } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) {
-	m_error = QFile::tr("Failed to open file \"%1\"")
+        m_error = QFile::tr("Failed to open file \"%1\"")
             .arg(m_source.getLocation());
     } else {
-	good = true;
+        good = true;
     }
 
     if (good) {
@@ -82,8 +82,8 @@
     }
 
     if (!good) {
-	delete m_file;
-	m_file = 0;
+        delete m_file;
+        m_file = 0;
     }
 }
 
--- a/data/fileio/QuickTimeFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +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-2007 Chris Cannam and QMUL.
-    
-    Based on QTAudioFile.cpp from SoundBite, copyright 2006
-    Chris Sutton and Mark Levy.
-    
-    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.
-*/
-
-#ifdef HAVE_QUICKTIME
-
-#include "QuickTimeFileReader.h"
-#include "base/Profiler.h"
-#include "base/ProgressReporter.h"
-#include "system/System.h"
-
-#include <QFileInfo>
-
-#ifdef _WIN32
-#include <QTML.h>
-#include <Movies.h>
-#else
-#include <QuickTime/QuickTime.h>
-#endif
-
-class QuickTimeFileReader::D
-{
-public:
-    D() : data(0), blockSize(1024) { }
-
-    MovieAudioExtractionRef      extractionSessionRef;
-    AudioBufferList              buffer;
-    float                       *data;
-    OSErr                        err; 
-    AudioStreamBasicDescription  asbd;
-    Movie                        movie;
-    int                          blockSize;
-};
-
-
-QuickTimeFileReader::QuickTimeFileReader(FileSource source,
-                                         DecodeMode decodeMode,
-                                         CacheMode mode,
-                                         sv_samplerate_t targetRate,
-                                         bool normalised,
-                                         ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate, normalised),
-    m_source(source),
-    m_path(source.getLocalFilename()),
-    m_d(new D),
-    m_reporter(reporter),
-    m_cancelled(false),
-    m_completion(0),
-    m_decodeThread(0)
-{
-    SVDEBUG << "QuickTimeFileReader: local path: \"" << m_path
-            << "\", decode mode: " << decodeMode << " ("
-            << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded")
-            << ")" << endl;
-
-    m_channelCount = 0;
-    m_fileRate = 0;
-
-    Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true);
-
-    long QTversion;
-
-#ifdef WIN32
-    InitializeQTML(0); // FIXME should check QT version
-#else
-    m_d->err = Gestalt(gestaltQuickTime,&QTversion);
-    if ((m_d->err != noErr) || (QTversion < 0x07000000)) {
-        m_error = QString("Failed to find compatible version of QuickTime (version 7 or above required)");
-        return;
-    }
-#endif 
-
-    EnterMovies();
-	
-    Handle dataRef; 
-    OSType dataRefType;
-
-//    CFStringRef URLString = CFStringCreateWithCString
- //       (0, m_path.toLocal8Bit().data(), 0);
-
-
-    QByteArray ba = m_path.toLocal8Bit();
-
-    CFURLRef url = CFURLCreateFromFileSystemRepresentation
-        (kCFAllocatorDefault,
-         (const UInt8 *)ba.data(),
-         (CFIndex)ba.length(),
-         false);
-
-
-//    m_d->err = QTNewDataReferenceFromURLCFString
-    m_d->err = QTNewDataReferenceFromCFURL
-        (url, 0, &dataRef, &dataRefType);
-
-    if (m_d->err) { 
-        m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err);
-        return;
-    }
-    
-    short fileID = movieInDataForkResID; 
-    short flags = 0; 
-    m_d->err = NewMovieFromDataRef
-        (&m_d->movie, flags, &fileID, dataRef, dataRefType);
-
-    DisposeHandle(dataRef);
-    if (m_d->err) { 
-        m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err); 
-        return;
-    }
-
-    Boolean isProtected = 0;
-    Track aTrack = GetMovieIndTrackType
-        (m_d->movie, 1, SoundMediaType,
-         movieTrackMediaType | movieTrackEnabledOnly);
-
-    if (aTrack) {
-        Media aMedia = GetTrackMedia(aTrack);	// get the track media
-        if (aMedia) {
-            MediaHandler mh = GetMediaHandler(aMedia);	// get the media handler we can query
-            if (mh) {
-                m_d->err = QTGetComponentProperty(mh,
-                                                  kQTPropertyClass_DRM,
-                                                  kQTDRMPropertyID_IsProtected,
-                                                  sizeof(Boolean), &isProtected,nil);
-            } else {
-                m_d->err = 1;
-            }
-        } else {
-            m_d->err = 1;
-        }
-    } else {
-        m_d->err = 1;
-    }
-	
-    if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) { 
-        m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err);
-        return;
-    } else if (!m_d->err && isProtected) { 
-        m_error = QString("File is protected with DRM");
-        return;
-    } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) {
-        cerr << "QuickTime: File is not protected with DRM" << endl;
-    }
-
-    if (m_d->movie) {
-        SetMovieActive(m_d->movie, TRUE);
-        m_d->err = GetMoviesError();
-        if (m_d->err) {
-            m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err);
-            return;
-        }
-    } else {
-	m_error = QString("Error in QuickTime decoder: Movie object not valid");
-	return;
-    }
-    
-    m_d->err = MovieAudioExtractionBegin
-        (m_d->movie, 0, &m_d->extractionSessionRef);
-    if (m_d->err) {
-        m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err);
-        return;
-    }
-
-    m_d->err = MovieAudioExtractionGetProperty
-        (m_d->extractionSessionRef,
-         kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
-         sizeof(m_d->asbd),
-         &m_d->asbd,
-         nil);
-
-    if (m_d->err) {
-        m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err);
-        return;
-    }
-	
-    m_channelCount = m_d->asbd.mChannelsPerFrame;
-    m_fileRate = m_d->asbd.mSampleRate;
-
-    cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << endl;
-
-    m_d->asbd.mFormatFlags =
-        kAudioFormatFlagIsFloat |
-        kAudioFormatFlagIsPacked |
-        kAudioFormatFlagsNativeEndian;
-    m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
-    m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame;
-    m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame;
-	
-    m_d->err = MovieAudioExtractionSetProperty
-        (m_d->extractionSessionRef,
-         kQTPropertyClass_MovieAudioExtraction_Audio,
-         kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
-         sizeof(m_d->asbd),
-         &m_d->asbd);
-
-    if (m_d->err) {
-        m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err);
-        m_channelCount = 0;
-        return;
-    }
-    m_d->buffer.mNumberBuffers = 1;
-    m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
-    m_d->buffer.mBuffers[0].mDataByteSize =
-        sizeof(float) * m_channelCount * m_d->blockSize;
-    m_d->data = new float[m_channelCount * m_d->blockSize];
-    m_d->buffer.mBuffers[0].mData = m_d->data;
-
-    initialiseDecodeCache();
-
-    if (decodeMode == DecodeAtOnce) {
-
-        if (m_reporter) {
-            connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
-            m_reporter->setMessage
-                (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
-        }
-
-        while (1) {
-            
-            UInt32 framesRead = m_d->blockSize;
-            UInt32 extractionFlags = 0;
-            m_d->err = MovieAudioExtractionFillBuffer
-                (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
-                 &extractionFlags);
-            if (m_d->err) {
-                m_error = QString("Error in QuickTime decoding: code %1")
-                    .arg(m_d->err);
-                break;
-            }
-
-            //!!! progress?
-
-//    cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl;
-
-            // QuickTime buffers are interleaved unless specified otherwise
-            addSamplesToDecodeCache(m_d->data, framesRead);
-
-            if (framesRead < m_d->blockSize) break;
-        }
-        
-        finishDecodeCache();
-        endSerialised();
-
-        m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
-        if (m_d->err) {
-            m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
-        }
-
-        m_completion = 100;
-
-    } else {
-        if (m_reporter) m_reporter->setProgress(100);
-
-        if (m_channelCount > 0) {
-            m_decodeThread = new DecodeThread(this);
-            m_decodeThread->start();
-        }
-    }
-
-    cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error << "\"" << endl;
-}
-
-QuickTimeFileReader::~QuickTimeFileReader()
-{
-    SVDEBUG << "QuickTimeFileReader::~QuickTimeFileReader" << endl;
-
-    if (m_decodeThread) {
-        m_cancelled = true;
-        m_decodeThread->wait();
-        delete m_decodeThread;
-    }
-
-    SetMovieActive(m_d->movie, FALSE);
-    DisposeMovie(m_d->movie);
-
-    delete[] m_d->data;
-    delete m_d;
-}
-
-void
-QuickTimeFileReader::cancelled()
-{
-    m_cancelled = true;
-}
-
-void
-QuickTimeFileReader::DecodeThread::run()
-{
-    if (m_reader->m_cacheMode == CacheInTemporaryFile) {
-        m_reader->m_completion = 1;
-        m_reader->startSerialised("QuickTimeFileReader::Decode");
-    }
-
-    while (1) {
-            
-        UInt32 framesRead = m_reader->m_d->blockSize;
-        UInt32 extractionFlags = 0;
-        m_reader->m_d->err = MovieAudioExtractionFillBuffer
-            (m_reader->m_d->extractionSessionRef, &framesRead,
-             &m_reader->m_d->buffer, &extractionFlags);
-        if (m_reader->m_d->err) {
-            m_reader->m_error = QString("Error in QuickTime decoding: code %1")
-                .arg(m_reader->m_d->err);
-            break;
-        }
-       
-        // QuickTime buffers are interleaved unless specified otherwise
-        m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
-        
-        if (framesRead < m_reader->m_d->blockSize) break;
-    }
-        
-    m_reader->finishDecodeCache();
-    
-    m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
-    if (m_reader->m_d->err) {
-        m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
-    }
-    
-    m_reader->m_completion = 100;
-    m_reader->endSerialised();
-} 
-
-void
-QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
-{
-    extensions.insert("aiff");
-    extensions.insert("aif");
-    extensions.insert("au");
-    extensions.insert("avi");
-    extensions.insert("m4a");
-    extensions.insert("m4b");
-    extensions.insert("m4p");
-    extensions.insert("m4v");
-    extensions.insert("mov");
-    extensions.insert("mp3");
-    extensions.insert("mp4");
-    extensions.insert("wav");
-}
-
-bool
-QuickTimeFileReader::supportsExtension(QString extension)
-{
-    std::set<QString> extensions;
-    getSupportedExtensions(extensions);
-    return (extensions.find(extension.toLower()) != extensions.end());
-}
-
-bool
-QuickTimeFileReader::supportsContentType(QString type)
-{
-    return (type == "audio/x-aiff" ||
-            type == "audio/x-wav" ||
-            type == "audio/mpeg" ||
-            type == "audio/basic" ||
-            type == "audio/x-aac" ||
-            type == "video/mp4" ||
-            type == "video/quicktime");
-}
-
-bool
-QuickTimeFileReader::supports(FileSource &source)
-{
-    return (supportsExtension(source.getExtension()) ||
-            supportsContentType(source.getContentType()));
-}
-
-#endif
-
--- a/data/fileio/QuickTimeFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +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-2007 Chris Cannam and QMUL.
-
-    Based in part on QTAudioFile.h from SoundBite, copyright 2006
-    Chris Sutton and Mark Levy.
-    
-    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 _QUICKTIME_FILE_READER_H_
-#define _QUICKTIME_FILE_READER_H_
-
-#ifdef HAVE_QUICKTIME
-
-#include "CodedAudioFileReader.h"
-
-#include "base/Thread.h"
-
-#include <set>
-
-class ProgressReporter;
-
-class QuickTimeFileReader : public CodedAudioFileReader
-{
-    Q_OBJECT
-
-public:
-    enum DecodeMode {
-        DecodeAtOnce, // decode the file on construction, with progress
-        DecodeThreaded // decode in a background thread after construction
-    };
-
-    QuickTimeFileReader(FileSource source,
-                        DecodeMode decodeMode,
-                        CacheMode cacheMode,
-                        sv_samplerate_t targetRate = 0,
-                        bool normalised = false,
-                        ProgressReporter *reporter = 0);
-    virtual ~QuickTimeFileReader();
-
-    virtual QString getError() const { return m_error; }
-    virtual QString getLocation() const { return m_source.getLocation(); }
-    virtual QString getTitle() const { return m_title; }
-    
-    static void getSupportedExtensions(std::set<QString> &extensions);
-    static bool supportsExtension(QString ext);
-    static bool supportsContentType(QString type);
-    static bool supports(FileSource &source);
-
-    virtual int getDecodeCompletion() const { return m_completion; }
-
-    virtual bool isUpdating() const {
-        return m_decodeThread && m_decodeThread->isRunning();
-    }
-
-public slots:
-    void cancelled();
-
-protected:
-    FileSource m_source;
-    QString m_path;
-    QString m_error;
-    QString m_title;
-
-    class D;
-    D *m_d;
-
-    ProgressReporter *m_reporter;
-    bool m_cancelled;
-    int m_completion;
-
-    class DecodeThread : public Thread
-    {
-    public:
-        DecodeThread(QuickTimeFileReader *reader) : m_reader(reader) { }
-        virtual void run();
-
-    protected:
-        QuickTimeFileReader *m_reader; 
-    };
-
-    DecodeThread *m_decodeThread;
-};
-
-#endif
-
-#endif
--- a/data/fileio/WavFileReader.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/WavFileReader.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -25,13 +25,17 @@
 
 using namespace std;
 
-WavFileReader::WavFileReader(FileSource source, bool fileUpdating) :
+WavFileReader::WavFileReader(FileSource source,
+                             bool fileUpdating,
+                             Normalisation normalisation) :
     m_file(0),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_seekable(false),
     m_lastStart(0),
     m_lastCount(0),
+    m_normalisation(normalisation),
+    m_max(0.f),
     m_updating(fileUpdating)
 {
     m_frameCount = 0;
@@ -40,21 +44,26 @@
 
     m_fileInfo.format = 0;
     m_fileInfo.frames = 0;
+
+#ifdef Q_OS_WIN
+    m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
+#else
     m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
+#endif
 
     if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) {
-	SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
+        SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
                 << m_path << "\" ("
                 << sf_strerror(m_file) << ")" << endl;
 
-	if (m_file) {
-	    m_error = QString("Couldn't load audio file '%1':\n%2")
-		.arg(m_path).arg(sf_strerror(m_file));
-	} else {
-	    m_error = QString("Failed to open audio file '%1'")
-		.arg(m_path);
-	}
-	return;
+        if (m_file) {
+            m_error = QString("Couldn't load audio file '%1':\n%2")
+                .arg(m_path).arg(sf_strerror(m_file));
+        } else {
+            m_error = QString("Failed to open audio file '%1'")
+                .arg(m_path);
+        }
+        return;
     }
 
     if (m_fileInfo.channels > 0) {
@@ -82,9 +91,13 @@
             // and mark those (basically only non-adaptive WAVs).
             m_seekable = true;
         }
+
+        if (m_normalisation != Normalisation::None && !m_updating) {
+            m_max = getMax();
+        }
     }
 
-    SVDEBUG << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << endl;
+    SVDEBUG << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << ", normalisation " << int(m_normalisation) << endl;
 }
 
 WavFileReader::~WavFileReader()
@@ -101,7 +114,11 @@
 
     if (m_file) {
         sf_close(m_file);
+#ifdef Q_OS_WIN
+        m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
+#else
         m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
+#endif
         if (!m_file || m_fileInfo.channels <= 0) {
             SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
                     << sf_strerror(m_file) << ")" << endl;
@@ -127,11 +144,31 @@
 {
     updateFrameCount();
     m_updating = false;
+    if (m_normalisation != Normalisation::None) {
+        m_max = getMax();
+    }
 }
 
-vector<float>
+floatvec_t
 WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
+    floatvec_t frames = getInterleavedFramesUnnormalised(start, count);
+
+    if (m_normalisation == Normalisation::None || m_max == 0.f) {
+        return frames;
+    }
+
+    for (int i = 0; in_range_for(frames, i); ++i) {
+        frames[i] /= m_max;
+    }
+    
+    return frames;
+}
+
+floatvec_t
+WavFileReader::getInterleavedFramesUnnormalised(sv_frame_t start,
+                                                sv_frame_t count) const
+{
     static HitCount lastRead("WavFileReader: last read");
 
     if (count == 0) return {};
@@ -147,11 +184,11 @@
     if (start >= m_fileInfo.frames) {
 //        SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
 //                  << " > " << m_fileInfo.frames << endl;
-	return {};
+        return {};
     }
 
     if (start + count > m_fileInfo.frames) {
-	count = m_fileInfo.frames - start;
+        count = m_fileInfo.frames - start;
     }
 
     // Because WaveFileModel::getSummaries() is called separately for
@@ -175,7 +212,7 @@
         return {};
     }
 
-    vector<float> data;
+    floatvec_t data;
     sv_frame_t n = count * m_fileInfo.channels;
     data.resize(n);
 
@@ -191,6 +228,43 @@
     return data;
 }
 
+float
+WavFileReader::getMax() const
+{
+    if (!m_file || !m_channelCount) {
+        return 0.f;
+    }
+
+    // First try for a PEAK chunk
+
+    double sfpeak = 0.0;
+    if (sf_command(m_file, SFC_GET_SIGNAL_MAX, &sfpeak, sizeof(sfpeak))
+        == SF_TRUE) {
+        SVDEBUG << "File has a PEAK chunk reporting max level " << sfpeak
+                << endl;
+        return float(fabs(sfpeak));
+    }
+
+    // Failing that, read all the samples
+
+    float peak = 0.f;
+    sv_frame_t ix = 0, chunk = 65536;
+
+    while (ix < m_frameCount) {
+        auto frames = getInterleavedFrames(ix, chunk);
+        for (float x: frames) {
+            float level = fabsf(x);
+            if (level > peak) {
+                peak = level;
+            }
+        }
+        ix += chunk;
+    }
+
+    SVDEBUG << "Measured file peak max level as " << peak << endl;
+    return peak;
+}
+
 void
 WavFileReader::getSupportedExtensions(set<QString> &extensions)
 {
--- a/data/fileio/WavFileReader.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/WavFileReader.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,11 +13,16 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _WAV_FILE_READER_H_
-#define _WAV_FILE_READER_H_
+#ifndef SV_WAV_FILE_READER_H
+#define SV_WAV_FILE_READER_H
 
 #include "AudioFileReader.h"
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
 #include <sndfile.h>
 #include <QMutex>
 
@@ -36,7 +41,11 @@
 class WavFileReader : public AudioFileReader
 {
 public:
-    WavFileReader(FileSource source, bool fileUpdating = false);
+    enum class Normalisation { None, Peak };
+
+    WavFileReader(FileSource source,
+                  bool fileUpdating = false,
+                  Normalisation normalise = Normalisation::None);
     virtual ~WavFileReader();
 
     virtual QString getLocation() const { return m_source.getLocation(); }
@@ -50,7 +59,8 @@
      * Must be safe to call from multiple threads with different
      * arguments on the same object at the same time.
      */
-    virtual std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
+    virtual floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count)
+        const;
     
     static void getSupportedExtensions(std::set<QString> &extensions);
     static bool supportsExtension(QString ext);
@@ -75,11 +85,18 @@
     bool m_seekable;
 
     mutable QMutex m_mutex;
-    mutable std::vector<float> m_buffer;
+    mutable floatvec_t m_buffer;
     mutable sv_frame_t m_lastStart;
     mutable sv_frame_t m_lastCount;
 
+    Normalisation m_normalisation;
+    float m_max;
+
     bool m_updating;
+
+    floatvec_t getInterleavedFramesUnnormalised(sv_frame_t start,
+                                                sv_frame_t count) const;
+    float getMax() const;
 };
 
 #endif
--- a/data/fileio/WavFileWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/WavFileWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -19,16 +19,21 @@
 #include "base/Selection.h"
 #include "base/TempWriteFile.h"
 #include "base/Exceptions.h"
+#include "base/Debug.h"
+
+#include <bqvec/Allocators.h>
+#include <bqvec/VectorOps.h>
 
 #include <QFileInfo>
 
 #include <iostream>
 #include <cmath>
+#include <string>
 
 using namespace std;
 
 WavFileWriter::WavFileWriter(QString path,
-			     sv_samplerate_t sampleRate,
+                             sv_samplerate_t sampleRate,
                              int channels,
                              FileWriteMode mode) :
     m_path(path),
@@ -41,7 +46,7 @@
 
     int fileRate = int(round(m_sampleRate));
     if (m_sampleRate != sv_samplerate_t(fileRate)) {
-        cerr << "WavFileWriter: WARNING: Non-integer sample rate "
+        SVCERR << "WavFileWriter: WARNING: Non-integer sample rate "
              << m_sampleRate << " presented, rounding to " << fileRate
              << endl;
     }
@@ -50,25 +55,27 @@
     fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
 
     try {
+        QString writePath = m_path;
         if (mode == WriteToTemporary) {
             m_temp = new TempWriteFile(m_path);
-            m_file = sf_open(m_temp->getTemporaryFilename().toLocal8Bit(),
-                             SFM_WRITE, &fileInfo);
-            if (!m_file) {
-                cerr << "WavFileWriter: Failed to open file ("
-                          << sf_strerror(m_file) << ")" << endl;
-                m_error = QString("Failed to open audio file '%1' for writing")
-                    .arg(m_temp->getTemporaryFilename());
+            writePath = m_temp->getTemporaryFilename();
+        }
+#ifdef Q_OS_WIN
+        m_file = sf_wchar_open((LPCWSTR)writePath.utf16(), SFM_WRITE, &fileInfo);
+#else
+        m_file = sf_open(writePath.toLocal8Bit(), SFM_WRITE, &fileInfo);
+#endif
+        if (!m_file) {
+            SVCERR << "WavFileWriter: Failed to create float-WAV file of "
+                   << m_channels << " channels at rate " << fileRate << " ("
+                   << sf_strerror(m_file) << ")" << endl;
+            m_error = QString("Failed to open audio file '%1' for writing")
+                .arg(writePath);
+            if (m_temp) {
+                delete m_temp;
+                m_temp = 0;
             }
-        } else {
-            m_file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo);
-            if (!m_file) {
-                cerr << "WavFileWriter: Failed to open file ("
-                          << sf_strerror(m_file) << ")" << endl;
-                m_error = QString("Failed to open audio file '%1' for writing")
-                    .arg(m_path);
-            }
-        }            
+        }
     } catch (FileOperationFailed &f) {
         m_error = f.what();
         m_temp = 0;
@@ -119,59 +126,59 @@
     if (!m_file) {
         m_error = QString("Failed to write model to audio file '%1': File not open")
             .arg(getWriteFilename());
-	return false;
+        return false;
     }
 
     bool ownSelection = false;
     if (!selection) {
-	selection = new MultiSelection;
-	selection->setSelection(Selection(source->getStartFrame(),
-					  source->getEndFrame()));
+        selection = new MultiSelection;
+        selection->setSelection(Selection(source->getStartFrame(),
+                                          source->getEndFrame()));
         ownSelection = true;
     }
 
     sv_frame_t bs = 2048;
 
     for (MultiSelection::SelectionList::iterator i =
-	     selection->getSelections().begin();
-	 i != selection->getSelections().end(); ++i) {
-	
-	sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
+         selection->getSelections().begin();
+         i != selection->getSelections().end(); ++i) {
+        
+        sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
 
-	for (sv_frame_t f = f0; f < f1; f += bs) {
-	    
-	    sv_frame_t n = min(bs, f1 - f);
-            vector<float> interleaved(n * m_channels, 0.f);
+        for (sv_frame_t f = f0; f < f1; f += bs) {
+            
+            sv_frame_t n = min(bs, f1 - f);
+            floatvec_t interleaved(n * m_channels, 0.f);
 
-	    for (int c = 0; c < int(m_channels); ++c) {
-                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];
-		}
-	    }	    
+            for (int c = 0; c < int(m_channels); ++c) {
+                auto 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, interleaved.data(), 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")
-		    .arg(written).arg(n).arg(f);
-		break;
-	    }
-	}
+            if (written < n) {
+                m_error = QString("Only wrote %1 of %2 frames at file frame %3")
+                        .arg(written).arg(n).arg(f);
+                break;
+            }
+        }
     }
 
     if (ownSelection) delete selection;
 
     return isOK();
 }
-	
+        
 bool
-WavFileWriter::writeSamples(float **samples, sv_frame_t count)
+WavFileWriter::writeSamples(const float *const *samples, sv_frame_t count)
 {
     if (!m_file) {
         m_error = QString("Failed to write model to audio file '%1': File not open")
             .arg(getWriteFilename());
-	return false;
+        return false;
     }
 
     float *b = new float[count * m_channels];
@@ -192,7 +199,20 @@
 
     return isOK();
 }
-    
+
+bool
+WavFileWriter::putInterleavedFrames(const floatvec_t &frames)
+{
+    sv_frame_t count = frames.size() / m_channels;
+    float **samples =
+        breakfastquay::allocate_channels<float>(m_channels, count);
+    breakfastquay::v_deinterleave
+        (samples, frames.data(), m_channels, int(count));
+    bool result = writeSamples(samples, count);
+    breakfastquay::deallocate_channels(samples, m_channels);
+    return result;
+}
+
 bool
 WavFileWriter::close()
 {
--- a/data/fileio/WavFileWriter.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/WavFileWriter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,11 +13,16 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _WAV_FILE_WRITER_H_
-#define _WAV_FILE_WRITER_H_
+#ifndef SV_WAV_FILE_WRITER_H
+#define SV_WAV_FILE_WRITER_H
 
 #include <QString>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
 #include <sndfile.h>
 
 #include "base/BaseTypes.h"
@@ -59,7 +64,11 @@
     bool writeModel(DenseTimeValueModel *source,
                     MultiSelection *selection = 0);
 
-    bool writeSamples(float **samples, sv_frame_t count); // count per channel
+    /// Write samples from raw arrays; count is per-channel
+    bool writeSamples(const float *const *samples, sv_frame_t count);
+
+    /// As writeSamples, but compatible with WavFileReader api. More expensive.
+    bool putInterleavedFrames(const floatvec_t &frames);
 
     bool close();
 
--- a/data/fileio/test/AudioFileReaderTest.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/test/AudioFileReaderTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -32,13 +32,26 @@
 
 using namespace std;
 
-static QString audioDir = "svcore/data/fileio/test/testfiles";
-static QString diffDir  = "svcore/data/fileio/test/diffs";
-
 class AudioFileReaderTest : public QObject
 {
     Q_OBJECT
 
+private:
+    QString testDirBase;
+    QString audioDir;
+    QString diffDir;
+
+public:
+    AudioFileReaderTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        audioDir = base + "/audio";
+        diffDir = base + "/diffs";
+    }
+
+private:
     const char *strOf(QString s) {
         return strdup(s.toLocal8Bit().data());
     }
@@ -174,11 +187,12 @@
     void init()
     {
         if (!QDir(audioDir).exists()) {
-            cerr << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist" << endl;
+            QString cwd = QDir::currentPath();
+            SVCERR << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist (cwd = " << cwd << ")" << endl;
             QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found");
         }
         if (!QDir(diffDir).exists() && !QDir().mkpath(diffDir)) {
-            cerr << "ERROR: Audio diff directory \"" << diffDir << "\" does not exist and could not be created" << endl;
+            SVCERR << "ERROR: Audio diff directory \"" << diffDir << "\" does not exist and could not be created" << endl;
             QVERIFY2(QDir(diffDir).exists(), "Audio diff directory not found and could not be created");
         }
     }
@@ -240,17 +254,17 @@
                               AudioFileReaderFactory::GaplessMode::Gapless :
                               AudioFileReaderFactory::GaplessMode::Gappy);
 
-	AudioFileReader *reader =
-	    AudioFileReaderFactory::createReader
-	    (audioDir + "/" + format + "/" + audiofile, params);
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (audioDir + "/" + format + "/" + audiofile, params);
         
-	if (!reader) {
+        if (!reader) {
 #if ( QT_VERSION >= 0x050000 )
-	    QSKIP("Unsupported file, skipping");
+            QSKIP("Unsupported file, skipping");
 #else
-	    QSKIP("Unsupported file, skipping", SkipSingle);
+            QSKIP("Unsupported file, skipping", SkipSingle);
 #endif
-	}
+        }
 
         QString extension;
         sv_samplerate_t fileRate;
@@ -262,18 +276,22 @@
         QCOMPARE(reader->getNativeRate(), fileRate);
         QCOMPARE(reader->getSampleRate(), readRate);
 
-	AudioTestData tdata(readRate, channels);
-	
-	float *reference = tdata.getInterleavedData();
+        AudioTestData tdata(readRate, channels);
+        
+        float *reference = tdata.getInterleavedData();
         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).
-	vector<float> test = reader->getInterleavedFrames(0, refFrames + 5000);
-	sv_frame_t read = test.size() / channels;
+        
+        // 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).
+        floatvec_t test = reader->getInterleavedFrames(0, refFrames + 5000);
+
+        delete reader;
+        reader = 0;
+        
+        sv_frame_t read = test.size() / channels;
 
         bool perceptual = (extension == "mp3" ||
                            extension == "aac" ||
@@ -375,7 +393,7 @@
             diffFile += ".wav";
             diffFile = QDir(diffDir).filePath(diffFile);
             WavFileWriter diffWriter(diffFile, readRate, channels,
-                                     WavFileWriter::WriteToTarget); //!!! NB WriteToTemporary not working, why?
+                                     WavFileWriter::WriteToTemporary);
             QVERIFY(diffWriter.isOK());
 
             vector<vector<float>> diffs(channels);
@@ -398,17 +416,17 @@
             delete[] ptrs;
         }
             
-	for (int c = 0; c < channels; ++c) {
+        for (int c = 0; c < channels; ++c) {
 
             double maxDiff = 0.0;
             double totalDiff = 0.0;
             double totalSqrDiff = 0.0;
-	    int maxIndex = 0;
+            int maxIndex = 0;
 
-	    for (int i = 0; i < refFrames; ++i) {
+            for (int i = 0; i < refFrames; ++i) {
                 int ix = i + offset;
                 if (ix >= read) {
-                    cerr << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward, of " << refFrames << ", are lost)" << endl;
+                    SVCERR << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward, of " << refFrames << ", are lost)" << endl;
                     QVERIFY(ix < read);
                 }
 
@@ -418,10 +436,10 @@
                     continue;
                 }
                 
-		double diff = fabs(test[ix * channels + c] -
+                double diff = fabs(test[ix * channels + c] -
                                    reference[i * channels + c]);
 
-		totalDiff += diff;
+                totalDiff += diff;
                 totalSqrDiff += diff * diff;
                 
                 // in edge areas, record this only if it exceeds edgeLimit
@@ -435,25 +453,25 @@
                         maxDiff = diff;
                         maxIndex = i;
                     }
-		}
-	    }
+                }
+            }
                 
-	    double meanDiff = totalDiff / double(refFrames);
+            double meanDiff = totalDiff / double(refFrames);
             double rmsDiff = sqrt(totalSqrDiff / double(refFrames));
 
             /*
-	    cerr << "channel " << c << ": mean diff " << meanDiff << endl;
-	    cerr << "channel " << c << ":  rms diff " << rmsDiff << endl;
-	    cerr << "channel " << c << ":  max diff " << maxDiff << " at " << maxIndex << endl;
+        cerr << "channel " << c << ": mean diff " << meanDiff << endl;
+            cerr << "channel " << c << ":  rms diff " << rmsDiff << endl;
+            cerr << "channel " << c << ":  max diff " << maxDiff << " at " << maxIndex << endl;
             */            
             if (rmsDiff >= rmsLimit) {
-		cerr << "ERROR: for audiofile " << audiofile << ": RMS diff = " << rmsDiff << " for channel " << c << " (limit = " << rmsLimit << ")" << endl;
+                SVCERR << "ERROR: for audiofile " << audiofile << ": RMS diff = " << rmsDiff << " for channel " << c << " (limit = " << rmsLimit << ")" << endl;
                 QVERIFY(rmsDiff < rmsLimit);
             }
-	    if (maxDiff >= maxLimit) {
-		cerr << "ERROR: for audiofile " << audiofile << ": max diff = " << maxDiff << " at frame " << maxIndex << " of " << read << " on channel " << c << " (limit = " << maxLimit << ", edge limit = " << edgeLimit << ", mean diff = " << meanDiff << ", rms = " << rmsDiff << ")" << endl;
-		QVERIFY(maxDiff < maxLimit);
-	    }
+            if (maxDiff >= maxLimit) {
+                SVCERR << "ERROR: for audiofile " << audiofile << ": max diff = " << maxDiff << " at frame " << maxIndex << " of " << read << " on channel " << c << " (limit = " << maxLimit << ", edge limit = " << edgeLimit << ", mean diff = " << meanDiff << ", rms = " << rmsDiff << ")" << endl;
+                QVERIFY(maxDiff < maxLimit);
+            }
 
             // and check for spurious material at end
             
@@ -462,11 +480,11 @@
                 float quiet = 0.1f; //!!! allow some ringing - but let's come back to this, it should tail off
                 float mag = fabsf(test[ix * channels + c]);
                 if (mag > quiet) {
-                    cerr << "ERROR: audiofile " << audiofile << " contains spurious data after end of reference (found sample " << test[ix * channels + c] << " at index " << ix << " of channel " << c << " after reference+offset ended at " << refFrames+offset << ")" << endl;
+                    SVCERR << "ERROR: audiofile " << audiofile << " contains spurious data after end of reference (found sample " << test[ix * channels + c] << " at index " << ix << " of channel " << c << " after reference+offset ended at " << refFrames+offset << ")" << endl;
                     QVERIFY(mag < quiet);
                 }
             }
-	}
+        }
     }
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/AudioFileWriterTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,135 @@
+/* -*- 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_AUDIO_FILE_WRITER_H
+#define TEST_AUDIO_FILE_WRITER_H
+
+#include "../AudioFileReaderFactory.h"
+#include "../AudioFileReader.h"
+#include "../WavFileWriter.h"
+
+#include "AudioTestData.h"
+
+#include "bqvec/VectorOps.h"
+#include "bqvec/Allocators.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+using namespace breakfastquay;
+
+class AudioFileWriterTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString testDirBase;
+    QString outDir;
+
+    static const int rate = 44100;
+    
+public:
+    AudioFileWriterTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        outDir = base + "/outfiles";
+    }
+
+    const char *strOf(QString s) {
+        return strdup(s.toLocal8Bit().data());
+    }
+    
+    QString testName(bool direct, int channels) {
+        return QString("%1 %2 %3")
+            .arg(channels)
+            .arg(channels > 1 ? "channels" : "channel")
+            .arg(direct ? "direct" : "via temporary");
+    }
+
+private slots:
+    void init()
+    {
+        if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
+            SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl;
+            QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created");
+        }
+    }
+
+    void write_data()
+    {
+        QTest::addColumn<bool>("direct");
+        QTest::addColumn<int>("channels");
+        for (int direct = 0; direct <= 1; ++direct) {
+            for (int channels = 1; channels < 8; ++channels) {
+                if (channels == 1 || channels == 2 ||
+                    channels == 5 || channels == 8) {
+                    QString desc = testName(direct, channels);
+                    QTest::newRow(strOf(desc)) << (bool)direct << channels;
+                }
+            }
+        }
+    }
+    
+    void write()
+    {
+        QFETCH(bool, direct);
+        QFETCH(int, channels);
+
+        QString outfile = QString("%1/out-%2ch-%3.wav")
+            .arg(outDir).arg(channels).arg(direct ? "direct" : "via-temporary");
+        
+        WavFileWriter writer(outfile,
+                             rate,
+                             channels,
+                             direct ?
+                             WavFileWriter::WriteToTarget :
+                             WavFileWriter::WriteToTemporary);
+        QVERIFY(writer.isOK());
+
+        AudioTestData data(rate, channels);
+        data.generate();
+
+        sv_frame_t frameCount = data.getFrameCount();
+        float *interleaved = data.getInterleavedData();
+        float **nonInterleaved = allocate_channels<float>(channels, frameCount);
+        v_deinterleave(nonInterleaved, interleaved, channels, int(frameCount));
+        bool ok = writer.writeSamples(nonInterleaved, frameCount);
+        deallocate_channels(nonInterleaved, channels);
+        QVERIFY(ok);
+        
+        ok = writer.close();
+        QVERIFY(ok);
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *rereader =
+            AudioFileReaderFactory::createReader(outfile, params);
+        QVERIFY(rereader != nullptr);
+        
+        floatvec_t readFrames = rereader->getInterleavedFrames(0, frameCount);
+        floatvec_t expected(interleaved, interleaved + frameCount * channels);
+        QCOMPARE(readFrames, expected);
+
+        delete rereader;
+    }
+};
+
+#endif
--- a/data/fileio/test/AudioTestData.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/test/AudioTestData.h	Mon Sep 17 13:51:14 2018 +0100
@@ -38,74 +38,74 @@
 {
 public:
     AudioTestData(double rate, int channels) :
-	m_channelCount(channels),
-	m_duration(2.0),
-	m_sampleRate(rate),
-	m_sinFreq(600.0),
-	m_pulseFreq(2)
+        m_channelCount(channels),
+        m_duration(2.0),
+        m_sampleRate(rate),
+        m_sinFreq(600.0),
+        m_pulseFreq(2)
     {
-	m_frameCount = lrint(m_duration * m_sampleRate);
-	m_data = new float[m_frameCount * m_channelCount];
-	m_pulseWidth = 0.01 * m_sampleRate;
-	generate();
+        m_frameCount = lrint(m_duration * m_sampleRate);
+        m_data = new float[m_frameCount * m_channelCount];
+        m_pulseWidth = 0.01 * m_sampleRate;
+        generate();
     }
 
     ~AudioTestData() {
-	delete[] m_data;
+        delete[] m_data;
     }
 
     void generate() {
 
-	double 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) {
+        for (int i = 0; i < m_frameCount; ++i) {
+            for (int c = 0; c < m_channelCount; ++c) {
 
-		double s = 0.0;
+                double s = 0.0;
 
-		if (c == 0) {
+                if (c == 0) {
 
-		    double phase = (i * m_sinFreq * 2.0 * M_PI) / m_sampleRate;
-		    s = sin(phase);
+                    double phase = (i * m_sinFreq * 2.0 * M_PI) / m_sampleRate;
+                    s = sin(phase);
 
-		} else if (c == 1) {
+                } else if (c == 1) {
 
-		    int pulseNo = int((i * m_pulseFreq) / m_sampleRate);
-		    int index = int(round((i * m_pulseFreq) -
+                    int pulseNo = int((i * m_pulseFreq) / m_sampleRate);
+                    int index = int(round((i * m_pulseFreq) -
                                           (m_sampleRate * pulseNo)));
-		    if (index < m_pulseWidth) {
-			s = 1.0 - fabs(hpw - index) / hpw;
-			if (pulseNo % 2) s = -s;
-		    }
+                    if (index < m_pulseWidth) {
+                        s = 1.0 - fabs(hpw - index) / hpw;
+                        if (pulseNo % 2) s = -s;
+                    }
 
-		} else {
+                } else {
 
-		    s = c / 20.0;
-		}
+                    s = c / 20.0;
+                }
 
-		m_data[i * m_channelCount + c] = float(s);
-	    }
-	}
+                m_data[i * m_channelCount + c] = float(s);
+            }
+        }
     }
 
     float *getInterleavedData() const {
-	return m_data;
+        return m_data;
     }
 
     sv_frame_t getFrameCount() const { 
-	return m_frameCount;
+        return m_frameCount;
     }
 
     int getChannelCount() const {
-	return m_channelCount;
+        return m_channelCount;
     }
 
     sv_samplerate_t getSampleRate () const {
-	return m_sampleRate;
+        return m_sampleRate;
     }
 
     double getDuration() const { // seconds
-	return m_duration;
+        return m_duration;
     }
 
 private:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/CSVFormatTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,276 @@
+/* -*- 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_CSV_FORMAT_H
+#define TEST_CSV_FORMAT_H
+
+// Tests for the code that guesses the most likely format for parsing a CSV file
+
+#include "../CSVFormat.h"
+
+#include "base/Debug.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class CSVFormatTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QDir csvDir;
+
+public:
+    CSVFormatTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        csvDir = QDir(base + "/csv");
+    }
+
+private slots:
+    void init() {
+        if (!csvDir.exists()) {
+            SVCERR << "ERROR: CSV test file directory \"" << csvDir.absolutePath() << "\" does not exist" << endl;
+            QVERIFY2(csvDir.exists(), "CSV test file directory not found");
+        }
+    }
+
+    void separatorComma() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("separator-comma.csv")));
+        QCOMPARE(f.getSeparator(), QChar(','));
+        QCOMPARE(f.getColumnCount(), 3);
+    }
+    
+    void separatorTab() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("separator-tab.csv")));
+        QCOMPARE(f.getSeparator(), QChar('\t'));
+        QCOMPARE(f.getColumnCount(), 3);
+    }
+    
+    void separatorPipe() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("separator-pipe.csv")));
+        QCOMPARE(f.getSeparator(), QChar('|'));
+        // differs from the others
+        QCOMPARE(f.getColumnCount(), 4);
+    }
+    
+    void separatorSpace() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("separator-space.csv")));
+        QCOMPARE(f.getSeparator(), QChar(' '));
+        // NB fields are separated by 1 or more spaces, not necessarily exactly 1
+        QCOMPARE(f.getColumnCount(), 3);
+    }
+    
+    void separatorColon() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("separator-colon.csv")));
+        QCOMPARE(f.getSeparator(), QChar(':'));
+        QCOMPARE(f.getColumnCount(), 3);
+    }
+    
+    void comment() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("comment.csv")));
+        QCOMPARE(f.getSeparator(), QChar(','));
+        QCOMPARE(f.getColumnCount(), 4);
+    }
+
+    void qualities() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("column-qualities.csv")));
+        QCOMPARE(f.getSeparator(), QChar(','));
+        QCOMPARE(f.getColumnCount(), 7);
+        QList<CSVFormat::ColumnQualities> q = f.getColumnQualities();
+        QList<CSVFormat::ColumnQualities> expected;
+        expected << 0;
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric |
+                                               CSVFormat::ColumnIntegral |
+                                               CSVFormat::ColumnIncreasing);
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric |
+                                               CSVFormat::ColumnIntegral |
+                                               CSVFormat::ColumnIncreasing |
+                                               CSVFormat::ColumnLarge);
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric);
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric |
+                                               CSVFormat::ColumnIncreasing);
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric |
+                                               CSVFormat::ColumnSmall |
+                                               CSVFormat::ColumnSigned);
+        expected << CSVFormat::ColumnQualities(CSVFormat::ColumnNumeric |
+                                               CSVFormat::ColumnIntegral |
+                                               CSVFormat::ColumnIncreasing |
+                                               CSVFormat::ColumnNearEmpty);
+        QCOMPARE(q, expected);
+    }
+
+    void modelType1DSamples() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-1d-samples.csv")));
+        QCOMPARE(f.getColumnCount(), 1);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeAudioFrames);
+        QCOMPARE(f.getModelType(), CSVFormat::OneDimensionalModel);
+    }
+
+    void modelType1DSeconds() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-1d-seconds.csv")));
+        QCOMPARE(f.getColumnCount(), 2);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnLabel);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeSeconds);
+        QCOMPARE(f.getModelType(), CSVFormat::OneDimensionalModel);
+    }
+
+    void modelType2DSamples() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-samples.csv")));
+        QCOMPARE(f.getColumnCount(), 2);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeAudioFrames);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModel);
+    }
+ 
+    void modelType2DSeconds() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-seconds.csv")));
+        QCOMPARE(f.getColumnCount(), 2);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeSeconds);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModel);
+    }
+    
+    void modelType2DImplicit() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-implicit.csv")));
+        QCOMPARE(f.getColumnCount(), 1);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ImplicitTiming);
+    }
+    
+    void modelType2DEndTimeSamples() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-endtime-samples.csv")));
+        QCOMPARE(f.getColumnCount(), 3);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnEndTime);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeAudioFrames);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModelWithDuration);
+    }
+    
+    void modelType2DEndTimeSeconds() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-endtime-seconds.csv")));
+        QCOMPARE(f.getColumnCount(), 3);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnEndTime);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeSeconds);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModelWithDuration);
+    }
+    
+    void modelType2DDurationSamples() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-duration-samples.csv")));
+        QCOMPARE(f.getColumnCount(), 3);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnDuration);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeAudioFrames);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModelWithDuration);
+    }
+        
+    void modelType2DDurationSeconds() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-2d-duration-seconds.csv")));
+        QCOMPARE(f.getColumnCount(), 3);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnDuration);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeSeconds);
+        QCOMPARE(f.getModelType(), CSVFormat::TwoDimensionalModelWithDuration);
+    }
+        
+    void modelType3DSamples() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-3d-samples.csv")));
+        QCOMPARE(f.getColumnCount(), 7);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(3), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(4), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(5), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(6), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeAudioFrames);
+        QCOMPARE(f.getModelType(), CSVFormat::ThreeDimensionalModel);
+    }
+         
+    void modelType3DSeconds() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-3d-seconds.csv")));
+        QCOMPARE(f.getColumnCount(), 7);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnStartTime);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(3), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(4), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(5), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(6), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ExplicitTiming);
+        QCOMPARE(f.getTimeUnits(), CSVFormat::TimeSeconds);
+        QCOMPARE(f.getModelType(), CSVFormat::ThreeDimensionalModel);
+    }
+         
+    void modelType3DImplicit() {
+        CSVFormat f;
+        QVERIFY(f.guessFormatFor(csvDir.filePath("model-type-3d-implicit.csv")));
+        QCOMPARE(f.getColumnCount(), 6);
+        QCOMPARE(f.getColumnPurpose(0), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(1), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(2), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(3), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(4), CSVFormat::ColumnValue);
+        QCOMPARE(f.getColumnPurpose(5), CSVFormat::ColumnValue);
+        QCOMPARE(f.getTimingType(), CSVFormat::ImplicitTiming);
+        QCOMPARE(f.getModelType(), CSVFormat::ThreeDimensionalModel);
+    }
+        
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/CSVStreamWriterTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,328 @@
+/* -*- 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 2017 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_CSV_STREAM_H
+#define TEST_CSV_STREAM_H
+
+#include <QtTest>
+#include <QObject>
+#include <sstream>
+#include <functional>
+
+#include "base/ProgressReporter.h"
+#include "base/DataExportOptions.h"
+#include "base/Selection.h"
+#include "data/model/NoteModel.h"
+#include "../CSVStreamWriter.h"
+#include "../../model/test/MockWaveModel.h"
+
+class StubReporter : public ProgressReporter
+{
+public:
+    StubReporter( std::function<bool()> isCancelled )
+        : m_isCancelled(isCancelled) {}
+    bool isDefinite() const override { return true; }
+    void setDefinite(bool) override {}
+    bool wasCancelled() const override { return m_isCancelled(); }
+    void setMessage(QString) override {}
+    void setProgress(int p) override
+    { 
+        ++m_calls;
+        m_percentageLog.push_back(p);
+    }
+
+    size_t getCallCount() const { return m_calls; }
+    std::vector<int> getPercentageLog() const { return m_percentageLog; }
+    void reset() { m_calls = 0; }
+private:
+    size_t m_calls = 0;
+    std::function<bool()> m_isCancelled;
+    std::vector<int> m_percentageLog;
+};
+
+class CSVStreamWriterTest : public QObject
+{
+    Q_OBJECT
+public:
+    std::string getExpectedString()
+    {
+        return
+        {
+          "0,0,0\n"
+          "1,0,0\n"
+          "2,0,0\n"
+          "3,0,0\n"
+          "4,1,1\n"
+          "5,1,1\n"
+          "6,1,1\n"
+          "7,1,1\n"
+          "8,1,1\n"
+          "9,1,1\n"
+          "10,1,1\n"
+          "11,1,1\n"
+          "12,1,1\n"
+          "13,1,1\n"
+          "14,1,1\n"
+          "15,1,1\n"
+          "16,1,1\n"
+          "17,1,1\n"
+          "18,1,1\n"
+          "19,1,1\n"
+          "20,0,0\n"
+          "21,0,0\n"
+          "22,0,0\n"
+          "23,0,0"
+        };
+    }
+
+private slots:
+    void simpleValidOutput()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+
+        std::ostringstream oss;
+        const auto result = CSVStreamWriter::writeInChunks(oss, mwm);
+        QVERIFY( oss.str() == getExpectedString() );
+        QVERIFY( result );
+    }
+
+    void callsReporterCorrectTimes()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+        StubReporter reporter { []() -> bool { return false; } };
+        const auto expected = getExpectedString();
+
+        std::ostringstream oss;
+        const auto writeStreamWithBlockSize = [&](int blockSize) {
+            return CSVStreamWriter::writeInChunks(
+                oss,
+                mwm,
+                &reporter,
+                ",",
+                DataExportDefaults,
+                blockSize
+            );
+        };
+
+        const auto reset = [&]() {
+            oss.str({});
+            reporter.reset();
+        };
+
+        const auto nonIntegerMultipleResult = writeStreamWithBlockSize(5);
+        QVERIFY( nonIntegerMultipleResult );
+        QVERIFY( reporter.getCallCount() == 5 /* 4.8 rounded up */ );
+        QVERIFY( oss.str() == expected );
+        reset();
+
+        const auto integerMultiple = writeStreamWithBlockSize(2);
+        QVERIFY( integerMultiple );
+        QVERIFY( reporter.getCallCount() == 12 );
+        QVERIFY( oss.str() == expected );
+        reset();
+
+        const auto largerThanNumberOfSamples = writeStreamWithBlockSize(100);
+        QVERIFY( largerThanNumberOfSamples );
+        QVERIFY( reporter.getCallCount() == 1 );
+        QVERIFY( oss.str() == expected );
+        reset();
+
+        const auto zero = writeStreamWithBlockSize(0);
+        QVERIFY( zero == false );
+        QVERIFY( reporter.getCallCount() == 0 );
+    }
+
+    void isCancellable()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+        StubReporter reporter { []() -> bool { return true; } };
+
+        std::ostringstream oss;
+        const auto cancelImmediately = CSVStreamWriter::writeInChunks(
+            oss,
+            mwm,
+            &reporter,
+            ",",
+            DataExportDefaults,
+            4
+        );
+        QVERIFY( cancelImmediately == false );
+        QVERIFY( reporter.getCallCount() == 0 );
+
+        StubReporter cancelMidway { 
+            [&]() { return cancelMidway.getCallCount() == 3; } 
+        };
+        const auto cancelledMidway = CSVStreamWriter::writeInChunks(
+            oss,
+            mwm,
+            &cancelMidway,
+            ",",
+            DataExportDefaults,
+            4
+        );
+        QVERIFY( cancelMidway.getCallCount() == 3 );
+        QVERIFY( cancelledMidway == false );
+    }
+
+    void zeroStartTimeReportsPercentageCorrectly()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+        StubReporter reporter { []() -> bool { return false; } };
+        std::ostringstream oss;
+        const auto succeeded = CSVStreamWriter::writeInChunks(
+            oss,
+            mwm,
+            &reporter,
+            ",",
+            DataExportDefaults,
+            4
+        );
+        QVERIFY( succeeded == true );
+        QVERIFY( reporter.getCallCount() == 6 );
+        const std::vector<int> expectedCallLog {
+            16,
+            33,
+            50,
+            66,
+            83,
+            100
+        };
+        QVERIFY( reporter.getPercentageLog() == expectedCallLog );
+        QVERIFY( oss.str() == getExpectedString() );
+    }
+
+    void nonZeroStartTimeReportsPercentageCorrectly()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+        StubReporter reporter { []() -> bool { return false; } };
+        std::ostringstream oss;
+        const auto writeSubSection = CSVStreamWriter::writeInChunks(
+            oss,
+            mwm,
+            {4, 20},
+            &reporter,
+            ",",
+            DataExportDefaults,
+            4
+        );
+        QVERIFY( reporter.getCallCount() == 4 );
+        const std::vector<int> expectedCallLog {
+            25,
+            50,
+            75,
+            100
+        };
+        QVERIFY( reporter.getPercentageLog() == expectedCallLog );
+        QVERIFY( writeSubSection == true );
+        const std::string expectedOutput {
+          "4,1,1\n"
+          "5,1,1\n"
+          "6,1,1\n"
+          "7,1,1\n"
+          "8,1,1\n"
+          "9,1,1\n"
+          "10,1,1\n"
+          "11,1,1\n"
+          "12,1,1\n"
+          "13,1,1\n"
+          "14,1,1\n"
+          "15,1,1\n"
+          "16,1,1\n"
+          "17,1,1\n"
+          "18,1,1\n"
+          "19,1,1"
+        };
+        QVERIFY( oss.str() == expectedOutput );
+    }
+
+    void multipleSelectionOutput()
+    {
+        MockWaveModel mwm({ DC, DC }, 16, 4);
+        StubReporter reporter { []() -> bool { return false; } };
+        std::ostringstream oss;
+        MultiSelection regions;
+        regions.addSelection({0, 2});
+        regions.addSelection({4, 6});
+        regions.addSelection({16, 18});
+//        qDebug("End frame: %lld", (long long int)mwm.getEndFrame());
+        const std::string expectedOutput {
+          "0,0,0\n"
+          "1,0,0\n"
+          "4,1,1\n"
+          "5,1,1\n"
+          "16,1,1\n"
+          "17,1,1"
+        };
+        const auto wroteMultiSection = CSVStreamWriter::writeInChunks(
+            oss,
+            mwm,
+            regions,
+            &reporter,
+            ",",
+            DataExportDefaults,
+            2
+        );
+        QVERIFY( wroteMultiSection == true );
+        QVERIFY( reporter.getCallCount() == 3 );
+        const std::vector<int> expectedCallLog { 33, 66, 100 };
+        QVERIFY( reporter.getPercentageLog() == expectedCallLog );
+//        qDebug("%s", oss.str().c_str());
+        QVERIFY( oss.str() == expectedOutput );
+    }
+
+    void writeSparseModel()
+    {
+        const auto pentatonicFromRoot = [](float midiPitch) {
+            return std::vector<float> {
+                0 + midiPitch,
+                2 + midiPitch,
+                4 + midiPitch,
+                7 + midiPitch,
+                9 + midiPitch
+            };
+        };
+        const auto cMajorPentatonic = pentatonicFromRoot(60.0);
+        NoteModel notes(8 /* sampleRate */, 4 /* resolution */);
+        sv_frame_t startFrame = 0;
+        for (const auto& note : cMajorPentatonic) {
+            notes.addPoint({startFrame, note, 4, 1.f, ""});
+            startFrame += 8;
+        }
+//        qDebug("Create Expected Output\n");
+
+        // NB. removed end line break
+        const auto expectedOutput = notes.toDelimitedDataString(",").trimmed();
+
+        StubReporter reporter { []() -> bool { return false; } };
+        std::ostringstream oss;
+//        qDebug("End frame: %lld", (long long int)notes.getEndFrame());
+//        qDebug("Write streaming\n");
+        const auto wroteSparseModel = CSVStreamWriter::writeInChunks(
+            oss,
+            notes,
+            &reporter,
+            ",",
+            DataExportDefaults,
+            2
+        );
+
+//        qDebug("\n%s\n", expectedOutput.toLocal8Bit().data());
+//        qDebug("\n%s\n", oss.str().c_str());
+        QVERIFY( wroteSparseModel == true );
+        QVERIFY( oss.str() == expectedOutput.toStdString() );
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/EncodingTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,267 @@
+/* -*- 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_AUDIO_ENCODINGS_H
+#define TEST_AUDIO_ENCODINGS_H
+
+// Quick tests for filename encodings and encoding of ID3 data. Not a
+// test of audio codecs.
+
+#include "../AudioFileReaderFactory.h"
+#include "../AudioFileReader.h"
+#include "../WavFileWriter.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+const char utf8_name_cdp_1[] = "Caf\303\251 de Paris";
+const char utf8_name_cdp_2[] = "Caf\303\251 de \351\207\215\345\272\206";
+const char utf8_name_tsprk[] = "T\303\253mple of Sp\303\266rks";
+const char utf8_name_sprkt[] = "\343\202\271\343\203\235\343\203\274\343\202\257\343\201\256\345\257\272\351\231\242";
+
+// Mapping between filename and expected title metadata field
+static const char *mapping[][2] = {
+    { "id3v2-iso-8859-1", utf8_name_cdp_1 },
+    { "id3v2-ucs-2", utf8_name_cdp_2 },
+    { utf8_name_tsprk, utf8_name_tsprk },
+    { utf8_name_sprkt, utf8_name_sprkt },
+};
+static const int mappingCount = 4;
+
+class EncodingTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString testDirBase;
+    QString encodingDir;
+    QString outDir;
+
+public:
+    EncodingTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        encodingDir = base + "/encodings";
+        outDir = base + "/outfiles";
+    }
+
+private:
+    const char *strOf(QString s) {
+        return strdup(s.toLocal8Bit().data());
+    }
+
+    void addAudioFiles() {
+        QTest::addColumn<QString>("audiofile");
+        QStringList files = QDir(encodingDir).entryList(QDir::Files);
+        foreach (QString filename, files) {
+            QTest::newRow(strOf(filename)) << filename;
+        }
+    }
+
+private slots:
+    void init()
+    {
+        if (!QDir(encodingDir).exists()) {
+            SVCERR << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl;
+            QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found");
+        }
+        if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
+            SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl;
+            QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created");
+        }
+    }
+
+    void readAudio_data() {
+        addAudioFiles();
+    }
+
+    void readAudio() {
+
+        // Ensure that we can open all the files
+        
+        QFETCH(QString, audiofile);
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (encodingDir + "/" + audiofile, params);
+
+        QVERIFY(reader != nullptr);
+
+        delete reader;
+    }
+
+    void readMetadata_data() {
+        addAudioFiles();
+    }
+    
+    void readMetadata() {
+        
+        // All files other than WAVs should have title metadata; check
+        // that the title matches whatever is in our mapping structure
+        // defined at the top
+        
+        QFETCH(QString, audiofile);
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (encodingDir + "/" + audiofile, params);
+
+        QVERIFY(reader != nullptr);
+
+        QStringList fileAndExt = audiofile.split(".");
+        QString file = fileAndExt[0];
+        QString extension = fileAndExt[1];
+
+        if (extension == "wav") {
+
+            // Nothing
+            
+            delete reader;
+
+        } else {
+
+#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
+            if (extension == "ogg") {
+                QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
+            }
+#endif
+            
+            auto blah = reader->getInterleavedFrames(0, 10);
+            
+            QString title = reader->getTitle();
+            QVERIFY(title != QString());
+
+            delete reader;
+
+            bool found = false;
+            for (int m = 0; m < mappingCount; ++m) {
+                if (file == QString::fromUtf8(mapping[m][0])) {
+                    found = true;
+                    QString expected = QString::fromUtf8(mapping[m][1]);
+                    if (title != expected) {
+                        SVCERR << "Title does not match expected: codepoints are" << endl;
+                        SVCERR << "Title (" << title.length() << "ch): ";
+                        for (int i = 0; i < title.length(); ++i) {
+                            SVCERR << title[i].unicode() << " ";
+                        }
+                        SVCERR << endl;
+                        SVCERR << "Expected (" << expected.length() << "ch): ";
+                        for (int i = 0; i < expected.length(); ++i) {
+                            SVCERR << expected[i].unicode() << " ";
+                        }
+                        SVCERR << endl;
+                    }
+                    QCOMPARE(title, expected);
+                    break;
+                }
+            }
+
+            if (!found) {
+                // Note that this can happen legitimately on Windows,
+                // where (for annoying VCS-related reasons) the test
+                // files may have a different filename encoding from
+                // the expected UTF-16. We check this properly in
+                // readWriteAudio below, by saving out the file to a
+                // name matching the metadata
+                SVCERR << "Couldn't find filename \""
+                     << file << "\" in title mapping array" << endl;
+                QSKIP("Couldn't find filename in title mapping array");
+            }
+        }
+    }
+
+    void readWriteAudio_data() {
+        addAudioFiles();
+    }
+
+    void readWriteAudio()
+    {
+        // For those files that have title metadata (i.e. all of them
+        // except the WAVs), read the title metadata and write a wav
+        // file (of arbitrary content) whose name matches that.  Then
+        // check that we can re-read it. This is intended to exercise
+        // systems on which the original test filename is miscoded (as
+        // can happen on Windows).
+        
+        QFETCH(QString, audiofile);
+
+        QStringList fileAndExt = audiofile.split(".");
+        QString file = fileAndExt[0];
+        QString extension = fileAndExt[1];
+
+        if (extension == "wav") {
+            return;
+        }
+
+#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
+        if (extension == "ogg") {
+            QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
+        }
+#endif
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (encodingDir + "/" + audiofile, params);
+        QVERIFY(reader != nullptr);
+
+        QString title = reader->getTitle();
+        QVERIFY(title != QString());
+
+        for (int useTemporary = 0; useTemporary <= 1; ++useTemporary) {
+        
+            QString outfile = outDir + "/" + file + ".wav";
+            WavFileWriter writer(outfile,
+                                 reader->getSampleRate(),
+                                 1,
+                                 useTemporary ?
+                                 WavFileWriter::WriteToTemporary :
+                                 WavFileWriter::WriteToTarget);
+
+            QVERIFY(writer.isOK());
+
+            floatvec_t data { 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0 };
+            const float *samples = data.data();
+            bool ok = writer.writeSamples(&samples, 8);
+            QVERIFY(ok);
+
+            ok = writer.close();
+            QVERIFY(ok);
+
+            AudioFileReader *rereader =
+                AudioFileReaderFactory::createReader(outfile, params);
+            QVERIFY(rereader != nullptr);
+
+            floatvec_t readFrames = rereader->getInterleavedFrames(0, 8);
+            QCOMPARE(readFrames, data);
+
+            delete rereader;
+        }
+
+        delete reader;
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/MIDIFileReaderTest.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,88 @@
+/* -*- 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 2013 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef TEST_MIDI_FILE_READER_H
+#define TEST_MIDI_FILE_READER_H
+
+#include "../MIDIFileReader.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include "base/Debug.h"
+
+#include <iostream>
+
+using namespace std;
+
+class MIDIFileReaderTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString testDirBase;
+    QString midiDir;
+
+    const char *strOf(QString s) {
+        return strdup(s.toLocal8Bit().data());
+    }
+
+public:
+    MIDIFileReaderTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        midiDir = base + "/midi";
+    }
+
+private slots:
+    void init()
+    {
+        if (!QDir(midiDir).exists()) {
+            SVCERR << "ERROR: MIDI file directory \"" << midiDir << "\" does not exist" << endl;
+            QVERIFY2(QDir(midiDir).exists(), "MIDI file directory not found");
+        }
+    }
+
+    void read_data()
+    {
+        QTest::addColumn<QString>("filename");
+        QStringList files = QDir(midiDir).entryList(QDir::Files);
+        foreach (QString filename, files) {
+            QTest::newRow(strOf(filename)) << filename;
+        }
+    }
+    
+    void read()
+    {
+        QFETCH(QString, filename);
+        QString path = midiDir + "/" + filename;
+        MIDIFileReader reader(path, nullptr, 44100);
+        Model *m = reader.load();
+        if (!m) {
+            SVCERR << "MIDI load failed for path: \"" << path << "\"" << endl;
+        }
+        QVERIFY(m != nullptr);
+        //!!! Ah, now here we could do something a bit more informative
+    }
+
+};
+
+#endif
+
Binary file data/fileio/test/audio/aac/32000-1.m4a has changed
Binary file data/fileio/test/audio/aac/44100-2.m4a has changed
Binary file data/fileio/test/audio/aiff/12000-6-16.aiff has changed
Binary file data/fileio/test/audio/aiff/48000-1-24.aiff has changed
Binary file data/fileio/test/audio/apple_lossless/32000-1.m4a has changed
Binary file data/fileio/test/audio/apple_lossless/44100-2.m4a has changed
Binary file data/fileio/test/audio/flac/44100-2.flac has changed
Binary file data/fileio/test/audio/mp3/32000-1.mp3 has changed
Binary file data/fileio/test/audio/mp3/44100-2.mp3 has changed
Binary file data/fileio/test/audio/ogg/32000-1.ogg has changed
Binary file data/fileio/test/audio/ogg/44100-2.ogg has changed
Binary file data/fileio/test/audio/wav/32000-1-16.wav has changed
Binary file data/fileio/test/audio/wav/44100-1-32.wav has changed
Binary file data/fileio/test/audio/wav/44100-2-16.wav has changed
Binary file data/fileio/test/audio/wav/44100-2-8.wav has changed
Binary file data/fileio/test/audio/wav/48000-1-16.wav has changed
Binary file data/fileio/test/audio/wav/8000-1-8.wav has changed
Binary file data/fileio/test/audio/wav/8000-2-16.wav has changed
Binary file data/fileio/test/audio/wav/8000-6-16.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/column-qualities.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,9 @@
+Text only,4,1024,45.6,45.7,-0.001,987
+Blah,5,2048,45.1,45.9,0.0123,
+
+# Include the odd blank line, space, and comment
+
+
+Parp, 6, 3072 , 44.7 ,52.1, 0.26,
+
+Toot,7,4096,42.2,57.9,0.0,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/comment.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+# This is a comment
+# This is a comment with various | possible | but not real	separators in it
+This is,the first,of the,real data lines
+# This,is,one,that,would,cause,more,columns,to,be,counted,if,it,were,real
+This is the,second
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-1d-samples.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+45678
+123239
+320130
+452103
+620301
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-1d-seconds.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+3.2	First thing
+4.4	Second thing
+5.5	Third thing
+6.3	Fourth thing
+7.8	Fifth thing
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-duration-samples.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,9 @@
+# Here we see something that looks like time - value - value, but the
+# time column is integral and so is exactly one of the value columns,
+# so we deduce that that one is actually duration. We can only do this
+# if the values are non-integral
+45678,123,4
+123239,4214,4.2
+320130,12312,0.4
+452103,4123,3.8
+620301,987654,-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-duration-seconds.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,8 @@
+# There are only very limited circumstances in which we deduce that we
+# have a time+duration 2d model with units of seconds - basically only
+# when the values column is entirely integer values
+1.1,4,620
+2.2,4.2,880
+3.3,0.4,440
+4.4,3.8,213
+5.5,-2.3,123
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-endtime-samples.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+45678,49000,4
+123239,330123,4.2
+320130,350000,0.4
+452103,540325,3.8
+620301,850000,-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-endtime-seconds.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+1.1,1.4,4
+2.2,3.1,4.2
+3.3,4.5,0.4
+4.4,4.6,3.8
+5.5,5.51,-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-implicit.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+4
+4.2
+0.4
+3.8
+-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-samples.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+45678,4
+123239,4.2
+320130,0.4
+452103,3.8
+620301,-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-2d-seconds.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+1.1,4
+2.2,4.2
+3.3,0.4
+4.4,3.8
+5.5,-2.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-3d-implicit.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,6 @@
+143.0,2.0,-1.3,0.0,0.0,1.0
+0.2,0.1,-3.0,0.0,0.1,0.143
+0.143,0.2,-3.1,0.0,0.0,0.1
+2.0,1.0,-0.3,0.0,1.0,143.0
+0.0,0.0,0.1,0.143,0.2,-3.1
+0.0,1.0,143.0,2.0,1.0,-0.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-3d-samples.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,6 @@
+22050,143.0,2.0,-1.3,0.0,0.0,1.0
+44100,0.2,0.1,-3.0,0.0,0.1,0.143
+66150,0.143,0.2,-3.1,0.0,0.0,0.1
+88200,2.0,1.0,-0.3,0.0,1.0,143.0
+110250,0.0,0.0,0.1,0.143,0.2,-3.1
+132300,0.0,1.0,143.0,2.0,1.0,-0.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/model-type-3d-seconds.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,6 @@
+1.1,143.0,2.0,-1.3,0.0,0.0,1.0
+2.2,0.2,0.1,-3.0,0.0,0.1,0.143
+3.3,0.143,0.2,-3.1,0.0,0.0,0.1
+4.4,2.0,1.0,-0.3,0.0,1.0,143.0
+5.5,0.0,0.0,0.1,0.143,0.2,-3.1
+6.6,0.0,1.0,143.0,2.0,1.0,-0.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/separator-colon.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,4 @@
+"This thing":"That thing":"The other thing"
+1:12,4:16,3
+2:14,2
+3:16,1:1901
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/separator-comma.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,4 @@
+This thing,That thing,The other thing
+1,12.4,16.3
+2,14.2
+3,16.1,"This, that\", and the other"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/separator-pipe.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,4 @@
+This thing|That thing|The other thing
+1|12,4|16,3
+2|14,2|And another|column
+3|16,1|1901|"Not another|column - we have four columns, not five"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/separator-space.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,4 @@
+"This thing" "That thing" "The other thing"
+1            12,4         16,3
+2            14,2
+3            16,1         1901
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/csv/separator-tab.csv	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,4 @@
+This thing	That thing	The other thing
+1	12,4	16,3
+2	14,2
+3	16,1	1901
Binary file data/fileio/test/encodings/Tëmple of Spörks.mp3 has changed
Binary file data/fileio/test/encodings/Tëmple of Spörks.ogg has changed
Binary file data/fileio/test/encodings/id3v2-iso-8859-1.mp3 has changed
Binary file data/fileio/test/encodings/id3v2-ucs-2.mp3 has changed
Binary file data/fileio/test/encodings/スポークの寺院.mp3 has changed
Binary file data/fileio/test/encodings/スポークの寺院.ogg has changed
--- a/data/fileio/test/files.pri	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/test/files.pri	Mon Sep 17 13:51:14 2018 +0100
@@ -1,7 +1,14 @@
 
 TEST_HEADERS += \
-	     AudioFileReaderTest.h \
-	     AudioTestData.h
-	     
+	../../model/test/MockWaveModel.h \
+	AudioFileReaderTest.h \
+	AudioFileWriterTest.h \
+	AudioTestData.h \
+	EncodingTest.h \
+	MIDIFileReaderTest.h \
+	CSVFormatTest.h \
+	CSVStreamWriterTest.h
+     
 TEST_SOURCES += \
-	     svcore-data-fileio-test.cpp
+	../../model/test/MockWaveModel.cpp \
+	svcore-data-fileio-test.cpp
Binary file data/fileio/test/midi/scale.mid has changed
Binary file data/fileio/test/midi/아브라카다브라.mid has changed
--- a/data/fileio/test/svcore-data-fileio-test.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/fileio/test/svcore-data-fileio-test.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -13,6 +13,11 @@
 */
 
 #include "AudioFileReaderTest.h"
+#include "AudioFileWriterTest.h"
+#include "EncodingTest.h"
+#include "MIDIFileReaderTest.h"
+#include "CSVFormatTest.h"
+#include "CSVStreamWriterTest.h"
 
 #include <QtTest>
 
@@ -22,22 +27,67 @@
 {
     int good = 0, bad = 0;
 
+    QString testDir;
+
+#ifdef Q_OS_WIN
+    // incredible to have to hardcode this, but I just can't figure out how to
+    // get QMAKE_POST_LINK to add an arg to its command successfully on Windows
+    testDir = "../sonic-visualiser/svcore/data/fileio/test";
+#endif
+
+    if (argc > 1) {
+        testDir = argv[1];
+    }
+
     QCoreApplication app(argc, argv);
-    app.setOrganizationName("Sonic Visualiser");
-    app.setApplicationName("test-fileio");
+    app.setOrganizationName("sonic-visualiser");
+    app.setApplicationName("test-svcore-data-fileio");
+
+    if (testDir != "") {
+        SVCERR << "Setting test directory base path to \"" << testDir << "\"" << endl;
+    }
 
     {
-	AudioFileReaderTest t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        AudioFileReaderTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
+        AudioFileWriterTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
+        EncodingTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
+        MIDIFileReaderTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
+        CSVFormatTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
+        CSVStreamWriterTest 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;
+        SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+        return 1;
     } else {
-	cerr << "All tests passed" << endl;
-	return 0;
+        SVCERR << "All tests passed" << endl;
+        return 0;
     }
 }
-
Binary file data/fileio/test/testfiles/aac/32000-1.m4a has changed
Binary file data/fileio/test/testfiles/aac/44100-2.m4a has changed
Binary file data/fileio/test/testfiles/aiff/12000-6-16.aiff has changed
Binary file data/fileio/test/testfiles/aiff/48000-1-24.aiff has changed
Binary file data/fileio/test/testfiles/apple_lossless/32000-1.m4a has changed
Binary file data/fileio/test/testfiles/apple_lossless/44100-2.m4a has changed
Binary file data/fileio/test/testfiles/flac/44100-2.flac has changed
Binary file data/fileio/test/testfiles/mp3/32000-1.mp3 has changed
Binary file data/fileio/test/testfiles/mp3/44100-2.mp3 has changed
Binary file data/fileio/test/testfiles/ogg/32000-1.ogg has changed
Binary file data/fileio/test/testfiles/ogg/44100-2.ogg has changed
Binary file data/fileio/test/testfiles/wav/32000-1-16.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-1-32.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-2-16.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-2-8.wav has changed
Binary file data/fileio/test/testfiles/wav/48000-1-16.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-1-8.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-2-16.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-6-16.wav has changed
--- a/data/midi/MIDIEvent.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/midi/MIDIEvent.h	Mon Sep 17 13:51:14 2018 +0100
@@ -123,9 +123,9 @@
               int eventCode,
               int data1 = 0,
               int data2 = 0) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_metaEventCode(0)
+        m_deltaTime(deltaTime),
+        m_duration(0),
+        m_metaEventCode(0)
     {
         if (eventCode < 0 || eventCode > 0xff ||
             data1 < 0 || data1 > 0xff ||
@@ -141,25 +141,25 @@
               MIDIByte eventCode,
               MIDIByte metaEventCode,
               const std::string &metaMessage) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(0),
-	m_data2(0),
-	m_metaEventCode(metaEventCode),
-	m_metaMessage(metaMessage)
+        m_deltaTime(deltaTime),
+        m_duration(0),
+        m_eventCode(eventCode),
+        m_data1(0),
+        m_data2(0),
+        m_metaEventCode(metaEventCode),
+        m_metaMessage(metaMessage)
     { }
 
     MIDIEvent(unsigned long deltaTime,
               MIDIByte eventCode,
               const std::string &sysEx) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(0),
-	m_data2(0),
-	m_metaEventCode(0),
-	m_metaMessage(sysEx)
+        m_deltaTime(deltaTime),
+        m_duration(0),
+        m_eventCode(eventCode),
+        m_data1(0),
+        m_data2(0),
+        m_metaEventCode(0),
+        m_metaMessage(sysEx)
     { }
 
     ~MIDIEvent() { }
@@ -167,8 +167,8 @@
     void setTime(const unsigned long &time) { m_deltaTime = time; }
     void setDuration(const unsigned long& duration) { m_duration = duration;}
     unsigned long addTime(const unsigned long &time) {
-	m_deltaTime += time;
-	return m_deltaTime;
+        m_deltaTime += time;
+        return m_deltaTime;
     }
 
     MIDIByte getMessageType() const
@@ -222,12 +222,12 @@
 public:
     MIDIException(QString message) throw() : m_message(message) {
         std::cerr << "WARNING: MIDI exception: "
-		  << message.toLocal8Bit().data() << std::endl;
+                  << message.toLocal8Bit().data() << std::endl;
     }
     virtual ~MIDIException() throw() { }
 
     virtual const char *what() const throw() {
-	return m_message.toLocal8Bit().data();
+        return m_message.toLocal8Bit().data();
     }
 
 protected:
--- a/data/midi/MIDIInput.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/midi/MIDIInput.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -20,16 +20,52 @@
 #include "system/System.h"
 
 MIDIInput::MIDIInput(QString name, FrameTimer *timer) :
-    m_rtmidi(),
+    m_rtmidi(0),
     m_frameTimer(timer),
     m_buffer(1023)
 {
     try {
-        m_rtmidi = new RtMidiIn(name.toStdString());
-        m_rtmidi->setCallback(staticCallback, this);
-        m_rtmidi->openPort(0, tr("Input").toStdString());
-    } catch (RtError e) {
-        e.printMessage();
+        std::vector<RtMidi::Api> apis;
+        RtMidi::getCompiledApi(apis);
+        RtMidi::Api preferredApi = RtMidi::UNSPECIFIED;
+        for (auto a: apis) {
+            if (a == RtMidi::UNSPECIFIED || a == RtMidi::RTMIDI_DUMMY) {
+                continue;
+            }
+            preferredApi = a;
+            break;
+        }
+        if (preferredApi == RtMidi::UNSPECIFIED) {
+            SVCERR << "ERROR: MIDIInput: No RtMidi APIs compiled in" << endl;
+        } else {
+
+            m_rtmidi = new RtMidiIn(preferredApi, name.toStdString());
+
+            int n = m_rtmidi->getPortCount();
+
+            if (n == 0) {
+
+                SVDEBUG << "NOTE: MIDIInput: No input ports available" << endl;
+                delete m_rtmidi;
+                m_rtmidi = 0;
+
+            } else {
+
+                m_rtmidi->setCallback(staticCallback, this);
+
+                SVDEBUG << "MIDIInput: Available ports are:" << endl;
+                for (int i = 0; i < n; ++i) {
+                    SVDEBUG << i << ". " << m_rtmidi->getPortName(i) << endl;
+                }
+                SVDEBUG << "MIDIInput: Using first port (\""
+                        << m_rtmidi->getPortName(0) << "\")" << endl;
+            
+                m_rtmidi->openPort(0, tr("Input").toStdString());
+            }
+        }
+        
+    } catch (const RtMidiError &e) {
+        SVCERR << "ERROR: RtMidi error: " << e.getMessage() << endl;
         delete m_rtmidi;
         m_rtmidi = 0;
     }
@@ -80,10 +116,10 @@
     int count = 0, max = 5;
     while (m_buffer.getWriteSpace() == 0) {
         if (count == max) {
-            cerr << "ERROR: MIDIInput::postEvent: MIDI event queue is full and not clearing -- abandoning incoming event" << endl;
+            SVCERR << "ERROR: MIDIInput::postEvent: MIDI event queue is full and not clearing -- abandoning incoming event" << endl;
             return;
         }
-        cerr << "WARNING: MIDIInput::postEvent: MIDI event queue (capacity " << m_buffer.getSize() << " is full!" << endl;
+        SVCERR << "WARNING: MIDIInput::postEvent: MIDI event queue (capacity " << m_buffer.getSize() << " is full!" << endl;
         SVDEBUG << "Waiting for something to be processed" << endl;
 #ifdef _WIN32
         Sleep(1);
--- a/data/midi/rtmidi/RtError.h	Mon Dec 12 15:18:52 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/************************************************************************/
-/*! \class RtError
-    \brief Exception handling class for RtAudio & RtMidi.
-
-    The RtError class is quite simple but it does allow errors to be
-    "caught" by RtError::Type. See the RtAudio and RtMidi
-    documentation to know which methods can throw an RtError.
-
-*/
-/************************************************************************/
-
-#ifndef RTERROR_H
-#define RTERROR_H
-
-#include <iostream>
-#include <string>
-
-class RtError
-{
-public:
-  //! Defined RtError types.
-  enum Type {
-    WARNING,           /*!< A non-critical error. */
-    DEBUG_WARNING,     /*!< A non-critical error which might be useful for debugging. */
-    UNSPECIFIED,       /*!< The default, unspecified error type. */
-    NO_DEVICES_FOUND,  /*!< No devices found on system. */
-    INVALID_DEVICE,    /*!< An invalid device ID was specified. */
-    INVALID_STREAM,    /*!< An invalid stream ID was specified. */
-    MEMORY_ERROR,      /*!< An error occured during memory allocation. */
-    INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
-    DRIVER_ERROR,      /*!< A system driver error occured. */
-    SYSTEM_ERROR,      /*!< A system error occured. */
-    THREAD_ERROR       /*!< A thread error occured. */
-  };
-
-protected:
-  std::string message_;
-  Type type_;
-
-public:
-  //! The constructor.
-  RtError(const std::string& message, Type type = RtError::UNSPECIFIED) : message_(message), type_(type) {}
-
-  //! The destructor.
-  virtual ~RtError(void) {};
-
-  //! Prints thrown error message to stderr.
-  virtual void printMessage(void) { std::cerr << '\n' << message_ << "\n\n"; }
-
-  //! Returns the thrown error message type.
-  virtual const Type& getType(void) { return type_; }
-
-  //! Returns the thrown error message string.
-  virtual const std::string& getMessage(void) { return message_; }
-
-  //! Returns the thrown error message as a C string.
-  virtual const char *getMessageString(void) { return message_.c_str(); }
-};
-
-#endif
--- a/data/midi/rtmidi/RtMidi.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/midi/rtmidi/RtMidi.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -8,7 +8,7 @@
     RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
 
     RtMidi: realtime MIDI i/o C++ classes
-    Copyright (c) 2003-2009 Gary P. Scavone
+    Copyright (c) 2003-2016 Gary P. Scavone
 
     Permission is hereby granted, free of charge, to any person
     obtaining a copy of this software and associated documentation files
@@ -22,8 +22,9 @@
     included in all copies or substantial portions of the Software.
 
     Any person wishing to distribute modifications to the Software is
-    requested to send the modifications to the original developer so that
-    they can be incorporated into the canonical version.
+    asked to send the modifications to the original developer so that
+    they can be incorporated into the canonical version.  This is,
+    however, not a binding provision of this license.
 
     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
@@ -35,72 +36,290 @@
 */
 /**********************************************************************/
 
-// RtMidi: Version 1.0.8
-
 #include "RtMidi.h"
 #include <sstream>
 
-using std::cerr;
-using std::endl;
+// CC be gung-ho about this here, assume upstream has it in hand
+#pragma GCC diagnostic ignored "-Wconversion"
+
+#if defined(__MACOSX_CORE__)
+  #if TARGET_OS_IPHONE
+    #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
+    #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos
+  #endif
+#endif
 
 //*********************************************************************//
-//  Common RtMidi Definitions
+//  RtMidi Definitions
 //*********************************************************************//
 
 RtMidi :: RtMidi()
-  : apiData_( 0 ), connected_( false )
+  : rtapi_(0)
 {
 }
 
-void RtMidi :: error( RtError::Type type )
+RtMidi :: ~RtMidi()
 {
-  if (type == RtError::WARNING) {
-    cerr << '\n' << errorString_ << "\n\n";
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+}
+
+std::string RtMidi :: getVersion( void ) throw()
+{
+  return std::string( RTMIDI_VERSION );
+}
+
+void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw()
+{
+  apis.clear();
+
+  // The order here will control the order of RtMidi's API search in
+  // the constructor.
+#if defined(__MACOSX_CORE__)
+  apis.push_back( MACOSX_CORE );
+#endif
+#if defined(__LINUX_ALSA__)
+  apis.push_back( LINUX_ALSA );
+#endif
+#if defined(__UNIX_JACK__)
+  apis.push_back( UNIX_JACK );
+#endif
+#if defined(__WINDOWS_MM__)
+  apis.push_back( WINDOWS_MM );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  apis.push_back( RTMIDI_DUMMY );
+#endif
+}
+
+//*********************************************************************//
+//  RtMidiIn Definitions
+//*********************************************************************//
+
+void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
+{
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+  if ( api == UNIX_JACK )
+    rtapi_ = new MidiInJack( clientName, queueSizeLimit );
+#endif
+#if defined(__LINUX_ALSA__)
+  if ( api == LINUX_ALSA )
+    rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );
+#endif
+#if defined(__WINDOWS_MM__)
+  if ( api == WINDOWS_MM )
+    rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );
+#endif
+#if defined(__MACOSX_CORE__)
+  if ( api == MACOSX_CORE )
+    rtapi_ = new MidiInCore( clientName, queueSizeLimit );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  if ( api == RTMIDI_DUMMY )
+    rtapi_ = new MidiInDummy( clientName, queueSizeLimit );
+#endif
+}
+
+RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
+  : RtMidi()
+{
+  if ( api != UNSPECIFIED ) {
+    // Attempt to open the specified API.
+    openMidiApi( api, clientName, queueSizeLimit );
+    if ( rtapi_ ) return;
+
+    // No compiled support for specified API value.  Issue a warning
+    // and continue as if no API was specified.
+    std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;
   }
-  else if (type == RtError::DEBUG_WARNING) {
+
+  // Iterate through the compiled APIs and return as soon as we find
+  // one with at least one port or we reach the end of the list.
+  std::vector< RtMidi::Api > apis;
+  getCompiledApi( apis );
+  for ( unsigned int i=0; i<apis.size(); i++ ) {
+    openMidiApi( apis[i], clientName, queueSizeLimit );
+    if ( rtapi_->getPortCount() ) break;
+  }
+
+  if ( rtapi_ ) return;
+
+  // It should not be possible to get here because the preprocessor
+  // definition __RTMIDI_DUMMY__ is automatically defined if no
+  // API-specific definitions are passed to the compiler. But just in
+  // case something weird happens, we'll throw an error.
+  std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";
+  throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiIn :: ~RtMidiIn() throw()
+{
+}
+
+
+//*********************************************************************//
+//  RtMidiOut Definitions
+//*********************************************************************//
+
+void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName )
+{
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+  if ( api == UNIX_JACK )
+    rtapi_ = new MidiOutJack( clientName );
+#endif
+#if defined(__LINUX_ALSA__)
+  if ( api == LINUX_ALSA )
+    rtapi_ = new MidiOutAlsa( clientName );
+#endif
+#if defined(__WINDOWS_MM__)
+  if ( api == WINDOWS_MM )
+    rtapi_ = new MidiOutWinMM( clientName );
+#endif
+#if defined(__MACOSX_CORE__)
+  if ( api == MACOSX_CORE )
+    rtapi_ = new MidiOutCore( clientName );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  if ( api == RTMIDI_DUMMY )
+    rtapi_ = new MidiOutDummy( clientName );
+#endif
+}
+
+RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName )
+{
+  if ( api != UNSPECIFIED ) {
+    // Attempt to open the specified API.
+    openMidiApi( api, clientName );
+    if ( rtapi_ ) return;
+
+    // No compiled support for specified API value.  Issue a warning
+    // and continue as if no API was specified.
+    std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;
+  }
+
+  // Iterate through the compiled APIs and return as soon as we find
+  // one with at least one port or we reach the end of the list.
+  std::vector< RtMidi::Api > apis;
+  getCompiledApi( apis );
+  for ( unsigned int i=0; i<apis.size(); i++ ) {
+    openMidiApi( apis[i], clientName );
+    if ( rtapi_->getPortCount() ) break;
+  }
+
+  if ( rtapi_ ) return;
+
+  // It should not be possible to get here because the preprocessor
+  // definition __RTMIDI_DUMMY__ is automatically defined if no
+  // API-specific definitions are passed to the compiler. But just in
+  // case something weird happens, we'll thrown an error.
+  std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";
+  throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiOut :: ~RtMidiOut() throw()
+{
+}
+
+//*********************************************************************//
+//  Common MidiApi Definitions
+//*********************************************************************//
+
+MidiApi :: MidiApi( void )
+  : apiData_( 0 ), connected_( false ), errorCallback_(0), errorCallbackUserData_(0)
+{
+}
+
+MidiApi :: ~MidiApi( void )
+{
+}
+
+void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )
+{
+    errorCallback_ = errorCallback;
+    errorCallbackUserData_ = userData;
+}
+
+void MidiApi :: error( RtMidiError::Type type, std::string errorString )
+{
+  if ( errorCallback_ ) {
+
+    if ( firstErrorOccurred_ )
+      return;
+
+    firstErrorOccurred_ = true;
+    const std::string errorMessage = errorString;
+
+    errorCallback_( type, errorMessage, errorCallbackUserData_);
+    firstErrorOccurred_ = false;
+    return;
+  }
+
+  if ( type == RtMidiError::WARNING ) {
+    std::cerr << '\n' << errorString << "\n\n";
+  }
+  else if ( type == RtMidiError::DEBUG_WARNING ) {
 #if defined(__RTMIDI_DEBUG__)
-    cerr << '\n' << errorString_ << "\n\n";
+    std::cerr << '\n' << errorString << "\n\n";
 #endif
   }
   else {
-    cerr << "\nRtMidi error: " << errorString_ << "\n\n";
-    throw RtError( errorString_, type );
+    std::cerr << '\n' << errorString << "\n\n";
+    throw RtMidiError( errorString, type );
   }
 }
 
 //*********************************************************************//
-//  Common RtMidiIn Definitions
+//  Common MidiInApi Definitions
 //*********************************************************************//
 
-RtMidiIn :: RtMidiIn( const std::string clientName ) : RtMidi()
+MidiInApi :: MidiInApi( unsigned int queueSizeLimit )
+  : MidiApi()
 {
-  this->initialize( clientName );
+  // Allocate the MIDI queue.
+  inputData_.queue.ringSize = queueSizeLimit;
+  if ( inputData_.queue.ringSize > 0 )
+    inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];
 }
 
-void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData )
+MidiInApi :: ~MidiInApi( void )
+{
+  // Delete the MIDI queue.
+  if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;
+}
+
+void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )
 {
   if ( inputData_.usingCallback ) {
-    errorString_ = "RtMidiIn::setCallback: a callback function is already set!";
-    error( RtError::WARNING );
+    errorString_ = "MidiInApi::setCallback: a callback function is already set!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   if ( !callback ) {
     errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";
-    error( RtError::WARNING );
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
-  inputData_.userCallback = (void *) callback;
+  inputData_.userCallback = callback;
   inputData_.userData = userData;
   inputData_.usingCallback = true;
 }
 
-void RtMidiIn :: cancelCallback()
+void MidiInApi :: cancelCallback()
 {
   if ( !inputData_.usingCallback ) {
     errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";
-    error( RtError::WARNING );
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
@@ -109,12 +328,7 @@
   inputData_.usingCallback = false;
 }
 
-void RtMidiIn :: setQueueSizeLimit( unsigned int queueSize )
-{
-  inputData_.queueLimit = queueSize;
-}
-
-void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
+void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
 {
   inputData_.ignoreFlags = 0;
   if ( midiSysex ) inputData_.ignoreFlags = 0x01;
@@ -122,43 +336,48 @@
   if ( midiSense ) inputData_.ignoreFlags |= 0x04;
 }
 
-double RtMidiIn :: getMessage( std::vector<unsigned char> *message )
+double MidiInApi :: getMessage( std::vector<unsigned char> *message )
 {
   message->clear();
 
   if ( inputData_.usingCallback ) {
     errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
-    error( RtError::WARNING );
+    error( RtMidiError::WARNING, errorString_ );
     return 0.0;
   }
 
-  if ( inputData_.queue.size() == 0 ) return 0.0;
+  if ( inputData_.queue.size == 0 ) return 0.0;
 
   // Copy queued message to the vector pointer argument and then "pop" it.
-  std::vector<unsigned char> *bytes = &(inputData_.queue.front().bytes);
+  std::vector<unsigned char> *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes);
   message->assign( bytes->begin(), bytes->end() );
-  double deltaTime = inputData_.queue.front().timeStamp;
-  inputData_.queue.pop();
+  double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp;
+  inputData_.queue.size--;
+  inputData_.queue.front++;
+  if ( inputData_.queue.front == inputData_.queue.ringSize )
+    inputData_.queue.front = 0;
 
   return deltaTime;
 }
 
 //*********************************************************************//
-//  Common RtMidiOut Definitions
+//  Common MidiOutApi Definitions
 //*********************************************************************//
 
-RtMidiOut :: RtMidiOut( const std::string clientName ) : RtMidi()
+MidiOutApi :: MidiOutApi( void )
+  : MidiApi()
 {
-  this->initialize( clientName );
 }
 
-
-//*********************************************************************//
-//  API: Macintosh OS-X
-//*********************************************************************//
-
-// API information found at:
-//   - http://developer. apple .com/audio/pdf/coreaudio.pdf 
+MidiOutApi :: ~MidiOutApi( void )
+{
+}
+
+// *************************************************** //
+//
+// OS/API-specific methods.
+//
+// *************************************************** //
 
 #if defined(__MACOSX_CORE__)
 
@@ -169,6 +388,7 @@
 // OS-X CoreMIDI header files.
 #include <CoreMIDI/CoreMIDI.h>
 #include <CoreAudio/HostTime.h>
+#include <CoreServices/CoreServices.h>
 
 // A structure to hold variables related to the CoreMIDI API
 // implementation.
@@ -178,16 +398,17 @@
   MIDIEndpointRef endpoint;
   MIDIEndpointRef destinationId;
   unsigned long long lastTime;
+  MIDISysexSendRequest sysexreq;
 };
 
 //*********************************************************************//
 //  API: OS-X
-//  Class Definitions: RtMidiIn
+//  Class Definitions: MidiInCore
 //*********************************************************************//
 
-void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef )
+static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )
 {
-  RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (procRef);
+  MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef);
   CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);
 
   unsigned char status;
@@ -195,7 +416,7 @@
   unsigned long long time;
 
   bool& continueSysex = data->continueSysex;
-  RtMidiIn::MidiMessage& message = data->message;
+  MidiInApi::MidiMessage& message = data->message;
 
   const MIDIPacket *packet = &list->packet[0];
   for ( unsigned int i=0; i<list->numPackets; ++i ) {
@@ -213,39 +434,53 @@
     if ( nBytes == 0 ) continue;
 
     // Calculate time stamp.
-    message.timeStamp = 0.0;
-    if ( data->firstMessage )
+
+    if ( data->firstMessage ) {
+      message.timeStamp = 0.0;
       data->firstMessage = false;
+    }
     else {
       time = packet->timeStamp;
+      if ( time == 0 ) { // this happens when receiving asynchronous sysex messages
+        time = AudioGetCurrentHostTime();
+      }
       time -= apiData->lastTime;
       time = AudioConvertHostTimeToNanos( time );
-      message.timeStamp = time * 0.000000001;
+      if ( !continueSysex )
+        message.timeStamp = time * 0.000000001;
     }
     apiData->lastTime = packet->timeStamp;
+    if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages
+      apiData->lastTime = AudioGetCurrentHostTime();
+    }
+    //std::cout << "TimeStamp = " << packet->timeStamp << std::endl;
 
     iByte = 0;
     if ( continueSysex ) {
       // We have a continuing, segmented sysex message.
       if ( !( data->ignoreFlags & 0x01 ) ) {
         // If we're not ignoring sysex messages, copy the entire packet.
-        for ( unsigned int j=0; j<nBytes; j++ )
+        for ( unsigned int j=0; j<nBytes; ++j )
           message.bytes.push_back( packet->data[j] );
       }
       continueSysex = packet->data[nBytes-1] != 0xF7;
 
-      if ( !continueSysex ) {
+      if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {
         // If not a continuing sysex message, invoke the user callback function or queue the message.
-        if ( data->usingCallback && message.bytes.size() > 0 ) {
+        if ( data->usingCallback ) {
           RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
           callback( message.timeStamp, &message.bytes, data->userData );
         }
         else {
           // As long as we haven't reached our queue size limit, push the message.
-          if ( data->queueLimit > data->queue.size() )
-            data->queue.push( message );
+          if ( data->queue.size < data->queue.ringSize ) {
+            data->queue.ring[data->queue.back++] = message;
+            if ( data->queue.back == data->queue.ringSize )
+              data->queue.back = 0;
+            data->queue.size++;
+          }
           else
-            cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
+            std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
         }
         message.bytes.clear();
       }
@@ -267,26 +502,24 @@
             iByte = nBytes;
           }
           else size = nBytes - iByte;
-          continueSysex =  packet->data[nBytes-1] != 0xF7;
+          continueSysex = packet->data[nBytes-1] != 0xF7;
         }
-        else if ( status < 0xF3 ) {
-          if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) {
-            // A MIDI time code message and we're ignoring it.
+        else if ( status == 0xF1 ) {
+            // A MIDI time code message
+           if ( data->ignoreFlags & 0x02 ) {
             size = 0;
-            iByte += 3;
-          }
-          else size = 3;
+            iByte += 2;
+           }
+           else size = 2;
         }
+        else if ( status == 0xF2 ) size = 3;
         else if ( status == 0xF3 ) size = 2;
-        else if ( status == 0xF8 ) {
-          size = 1;
-          if ( data->ignoreFlags & 0x02 ) {
-            // A MIDI timing tick message and we're ignoring it.
-            size = 0;
-            iByte += 3;
-          }
+        else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {
+          // A MIDI timing tick message and we're ignoring it.
+          size = 0;
+          iByte += 1;
         }
-        else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
+        else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {
           // A MIDI active sensing message and we're ignoring it.
           size = 0;
           iByte += 1;
@@ -304,10 +537,14 @@
             }
             else {
               // As long as we haven't reached our queue size limit, push the message.
-              if ( data->queueLimit > data->queue.size() )
-                data->queue.push( message );
+              if ( data->queue.size < data->queue.ringSize ) {
+                data->queue.ring[data->queue.back++] = message;
+                if ( data->queue.back == data->queue.ringSize )
+                  data->queue.back = 0;
+                data->queue.size++;
+              }
               else
-                cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
+                std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
             }
             message.bytes.clear();
           }
@@ -319,14 +556,33 @@
   }
 }
 
-void RtMidiIn :: initialize( const std::string& clientName )
+MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInCore :: ~MidiInCore( void )
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Cleanup.
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  MIDIClientDispose( data->client );
+  if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+  delete data;
+}
+
+void MidiInCore :: initialize( const std::string& clientName )
 {
   // Set up our client.
   MIDIClientRef client;
-  OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client );
+  CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+  OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
   if ( result != noErr ) {
-    errorString_ = "RtMidiIn::initialize: error creating OS-X MIDI client object.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific connection information.
@@ -335,27 +591,31 @@
   data->endpoint = 0;
   apiData_ = (void *) data;
   inputData_.apiData = (void *) data;
+  CFRelease(name);
 }
 
-void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName )
+void MidiInCore :: openPort( unsigned int portNumber, const std::string portName )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiInCore::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
   unsigned int nSrc = MIDIGetNumberOfSources();
   if (nSrc < 1) {
-    errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
-    error( RtError::NO_DEVICES_FOUND );
+    errorString_ = "MidiInCore::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-  std::ostringstream ost;
   if ( portNumber >= nSrc ) {
-    ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    std::ostringstream ost;
+    ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
   MIDIPortRef port;
@@ -365,17 +625,19 @@
                                          midiInputCallback, (void *)&inputData_, &port );
   if ( result != noErr ) {
     MIDIClientDispose( data->client );
-    errorString_ = "RtMidiIn::openPort: error creating OS-X MIDI input port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Get the desired input source identifier.
   MIDIEndpointRef endpoint = MIDIGetSource( portNumber );
-  if ( endpoint == NULL ) {
+  if ( endpoint == 0 ) {
     MIDIPortDispose( port );
     MIDIClientDispose( data->client );
-    errorString_ = "RtMidiIn::openPort: error getting MIDI input source reference.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Make the connection.
@@ -383,8 +645,9 @@
   if ( result != noErr ) {
     MIDIPortDispose( port );
     MIDIClientDispose( data->client );
-    errorString_ = "RtMidiIn::openPort: error connecting OS-X MIDI input port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific port information.
@@ -393,7 +656,7 @@
   connected_ = true;
 }
 
-void RtMidiIn :: openVirtualPort( const std::string portName )
+void MidiInCore :: openVirtualPort( const std::string portName )
 {
   CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
 
@@ -403,24 +666,197 @@
                                            CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
                                            midiInputCallback, (void *)&inputData_, &endpoint );
   if ( result != noErr ) {
-    errorString_ = "RtMidiIn::openVirtualPort: error creating virtual OS-X MIDI destination.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific connection information.
   data->endpoint = endpoint;
 }
 
-void RtMidiIn :: closePort( void )
+void MidiInCore :: closePort( void )
 {
-  if ( connected_ ) {
-    CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  if ( data->endpoint ) {
+    MIDIEndpointDispose( data->endpoint );
+  }
+
+  if ( data->port ) {
     MIDIPortDispose( data->port );
-    connected_ = false;
   }
+
+  connected_ = false;
 }
 
-RtMidiIn :: ~RtMidiIn()
+unsigned int MidiInCore :: getPortCount()
+{
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  return MIDIGetNumberOfSources();
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal )
+{
+  CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+  CFStringRef str;
+
+  // Begin with the endpoint's name.
+  str = NULL;
+  MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str );
+  if ( str != NULL ) {
+    CFStringAppend( result, str );
+    CFRelease( str );
+  }
+
+  MIDIEntityRef entity = 0;
+  MIDIEndpointGetEntity( endpoint, &entity );
+  if ( entity == 0 )
+    // probably virtual
+    return result;
+
+  if ( CFStringGetLength( result ) == 0 ) {
+    // endpoint name has zero length -- try the entity
+    str = NULL;
+    MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str );
+    if ( str != NULL ) {
+      CFStringAppend( result, str );
+      CFRelease( str );
+    }
+  }
+  // now consider the device's name
+  MIDIDeviceRef device = 0;
+  MIDIEntityGetDevice( entity, &device );
+  if ( device == 0 )
+    return result;
+
+  str = NULL;
+  MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str );
+  if ( CFStringGetLength( result ) == 0 ) {
+      CFRelease( result );
+      return str;
+  }
+  if ( str != NULL ) {
+    // if an external device has only one entity, throw away
+    // the endpoint name and just use the device name
+    if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) {
+      CFRelease( result );
+      return str;
+    } else {
+      if ( CFStringGetLength( str ) == 0 ) {
+        CFRelease( str );
+        return result;
+      }
+      // does the entity name already start with the device name?
+      // (some drivers do this though they shouldn't)
+      // if so, do not prepend
+        if ( CFStringCompareWithOptions( result, /* endpoint name */
+             str /* device name */,
+             CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) {
+        // prepend the device name to the entity name
+        if ( CFStringGetLength( result ) > 0 )
+          CFStringInsert( result, 0, CFSTR(" ") );
+        CFStringInsert( result, 0, str );
+      }
+      CFRelease( str );
+    }
+  }
+  return result;
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint )
+{
+  CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+  CFStringRef str;
+  OSStatus err;
+  int i;
+
+  // Does the endpoint have connections?
+  CFDataRef connections = NULL;
+  int nConnected = 0;
+  bool anyStrings = false;
+  err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections );
+  if ( connections != NULL ) {
+    // It has connections, follow them
+    // Concatenate the names of all connected devices
+    nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID);
+    if ( nConnected ) {
+      const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
+      for ( i=0; i<nConnected; ++i, ++pid ) {
+        MIDIUniqueID id = EndianS32_BtoN( *pid );
+        MIDIObjectRef connObject;
+        MIDIObjectType connObjectType;
+        err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType );
+        if ( err == noErr ) {
+          if ( connObjectType == kMIDIObjectType_ExternalSource  ||
+              connObjectType == kMIDIObjectType_ExternalDestination ) {
+            // Connected to an external device's endpoint (10.3 and later).
+            str = EndpointName( (MIDIEndpointRef)(connObject), true );
+          } else {
+            // Connected to an external device (10.2) (or something else, catch-
+            str = NULL;
+            MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str );
+          }
+          if ( str != NULL ) {
+            if ( anyStrings )
+              CFStringAppend( result, CFSTR(", ") );
+            else anyStrings = true;
+            CFStringAppend( result, str );
+            CFRelease( str );
+          }
+        }
+      }
+    }
+    CFRelease( connections );
+  }
+  if ( anyStrings )
+    return result;
+
+  CFRelease( result );
+
+  // Here, either the endpoint had no connections, or we failed to obtain names 
+  return EndpointName( endpoint, false );
+}
+
+std::string MidiInCore :: getPortName( unsigned int portNumber )
+{
+  CFStringRef nameRef;
+  MIDIEndpointRef portRef;
+  char name[128];
+
+  std::string stringName;
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  if ( portNumber >= MIDIGetNumberOfSources() ) {
+    std::ostringstream ost;
+    ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  portRef = MIDIGetSource( portNumber );
+  nameRef = ConnectedEndpointName(portRef);
+  CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
+  CFRelease( nameRef );
+
+  return stringName = name;
+}
+
+//*********************************************************************//
+//  API: OS-X
+//  Class Definitions: MidiOutCore
+//*********************************************************************//
+
+MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutCore :: ~MidiOutCore( void )
 {
   // Close a connection if it exists.
   closePort();
@@ -432,71 +868,16 @@
   delete data;
 }
 
-unsigned int RtMidiIn :: getPortCount()
-{
-  return MIDIGetNumberOfSources();
-}
-
-std::string RtMidiIn :: getPortName( unsigned int portNumber )
-{
-  CFStringRef nameRef;
-  MIDIEndpointRef portRef;
-  std::ostringstream ost;
-  char name[128];
-
-  if ( portNumber >= MIDIGetNumberOfSources() ) {
-    ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-  portRef = MIDIGetSource( portNumber );
-
-  MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
-  CFStringGetCString( nameRef, name, sizeof(name), 0);
-  CFRelease( nameRef );
-  std::string stringName = name;
-  return stringName;
-}
-
-//*********************************************************************//
-//  API: OS-X
-//  Class Definitions: RtMidiOut
-//*********************************************************************//
-
-unsigned int RtMidiOut :: getPortCount()
-{
-  return MIDIGetNumberOfDestinations();
-}
-
-std::string RtMidiOut :: getPortName( unsigned int portNumber )
-{
-  CFStringRef nameRef;
-  MIDIEndpointRef portRef;
-  std::ostringstream ost;
-  char name[128];
-
-  if ( portNumber >= MIDIGetNumberOfDestinations() ) {
-    ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-  portRef = MIDIGetDestination( portNumber );
-
-  MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
-  CFStringGetCString( nameRef, name, sizeof(name), 0);
-  CFRelease( nameRef );
-  std::string stringName = name;
-  return stringName;
-}
-
-void RtMidiOut :: initialize( const std::string& clientName )
+void MidiOutCore :: initialize( const std::string& clientName )
 {
   // Set up our client.
   MIDIClientRef client;
-  OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client );
+  CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+  OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
   if ( result != noErr ) {
-    errorString_ = "RtMidiOut::initialize: error creating OS-X MIDI client object.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific connection information.
@@ -504,27 +885,61 @@
   data->client = client;
   data->endpoint = 0;
   apiData_ = (void *) data;
+  CFRelease( name );
 }
 
-void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName )
+unsigned int MidiOutCore :: getPortCount()
+{
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  return MIDIGetNumberOfDestinations();
+}
+
+std::string MidiOutCore :: getPortName( unsigned int portNumber )
+{
+  CFStringRef nameRef;
+  MIDIEndpointRef portRef;
+  char name[128];
+
+  std::string stringName;
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  if ( portNumber >= MIDIGetNumberOfDestinations() ) {
+    std::ostringstream ost;
+    ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  portRef = MIDIGetDestination( portNumber );
+  nameRef = ConnectedEndpointName(portRef);
+  CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
+  CFRelease( nameRef );
+  
+  return stringName = name;
+}
+
+void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutCore::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
   unsigned int nDest = MIDIGetNumberOfDestinations();
   if (nDest < 1) {
-    errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!";
-    error( RtError::NO_DEVICES_FOUND );
+    errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-  std::ostringstream ost;
   if ( portNumber >= nDest ) {
-    ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    std::ostringstream ost;
+    ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
   MIDIPortRef port;
@@ -534,17 +949,19 @@
                                           &port );
   if ( result != noErr ) {
     MIDIClientDispose( data->client );
-    errorString_ = "RtMidiOut::openPort: error creating OS-X MIDI output port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Get the desired output port identifier.
   MIDIEndpointRef destination = MIDIGetDestination( portNumber );
-  if ( destination == NULL ) {
+  if ( destination == 0 ) {
     MIDIPortDispose( port );
     MIDIClientDispose( data->client );
-    errorString_ = "RtMidiOut::openPort: error getting MIDI output destination reference.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific connection information.
@@ -553,22 +970,28 @@
   connected_ = true;
 }
 
-void RtMidiOut :: closePort( void )
-{
-  if ( connected_ ) {
-    CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
-    MIDIPortDispose( data->port );
-    connected_ = false;
-  }
-}
-
-void RtMidiOut :: openVirtualPort( std::string portName )
+void MidiOutCore :: closePort( void )
 {
   CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
 
   if ( data->endpoint ) {
-    errorString_ = "RtMidiOut::openVirtualPort: a virtual output port already exists!";
-    error( RtError::WARNING );
+    MIDIEndpointDispose( data->endpoint );
+  }
+
+  if ( data->port ) {
+    MIDIPortDispose( data->port );
+  }
+
+  connected_ = false;
+}
+
+void MidiOutCore :: openVirtualPort( std::string portName )
+{
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  if ( data->endpoint ) {
+    errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
@@ -578,81 +1001,70 @@
                                       CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
                                       &endpoint );
   if ( result != noErr ) {
-    errorString_ = "RtMidiOut::initialize: error creating OS-X virtual MIDI source.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   // Save our api-specific connection information.
   data->endpoint = endpoint;
 }
 
-RtMidiOut :: ~RtMidiOut()
+void MidiOutCore :: sendMessage( std::vector<unsigned char> *message )
 {
-  // Close a connection if it exists.
-  closePort();
-
-  // Cleanup.
-  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
-  MIDIClientDispose( data->client );
-  if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
-  delete data;
-}
-
-void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
-{
-  // The CoreMidi documentation indicates a maximum PackList size of
-  // 64K, so we may need to break long sysex messages into pieces and
-  // send via separate lists.
+  // We use the MIDISendSysex() function to asynchronously send sysex
+  // messages.  Otherwise, we use a single CoreMidi MIDIPacket.
   unsigned int nBytes = message->size();
   if ( nBytes == 0 ) {
-    errorString_ = "RtMidiOut::sendMessage: no data in message argument!";      
-    error( RtError::WARNING );
+    errorString_ = "MidiOutCore::sendMessage: no data in message argument!";      
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
-  if ( nBytes > 3 && ( message->at(0) != 0xF0 ) ) {
-    errorString_ = "RtMidiOut::sendMessage: message format problem ... not sysex but > 3 bytes?";      
-    error( RtError::WARNING );
+  MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  OSStatus result;
+
+  if ( message->at(0) != 0xF0 && nBytes > 3 ) {
+    errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
-  unsigned int packetBytes, bytesLeft = nBytes;
-  unsigned int messageIndex = 0;
-  MIDITimeStamp timeStamp = 0;
-  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
-
-  while ( bytesLeft > 0 ) {
-
-    packetBytes = ( bytesLeft > 32736 ) ? 32736 : bytesLeft;
-    Byte buffer[packetBytes + 32]; // extra memory for other structure variables
-    MIDIPacketList *packetList = (MIDIPacketList *) buffer;
-    MIDIPacket *curPacket = MIDIPacketListInit( packetList );
-
-    curPacket = MIDIPacketListAdd( packetList, packetBytes+32, curPacket, timeStamp, packetBytes, (const Byte *) &message->at( messageIndex ) );
-    if ( !curPacket ) {
-      errorString_ = "RtMidiOut::sendMessage: could not allocate packet list";      
-      error( RtError::DRIVER_ERROR );
+  Byte buffer[nBytes+(sizeof(MIDIPacketList))];
+  ByteCount listSize = sizeof(buffer);
+  MIDIPacketList *packetList = (MIDIPacketList*)buffer;
+  MIDIPacket *packet = MIDIPacketListInit( packetList );
+
+  ByteCount remainingBytes = nBytes;
+  while (remainingBytes && packet) {
+    ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket
+    const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes );
+    packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr);
+    remainingBytes -= bytesForPacket; 
+  }
+
+  if ( !packet ) {
+    errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";      
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Send to any destinations that may have connected to us.
+  if ( data->endpoint ) {
+    result = MIDIReceived( data->endpoint, packetList );
+    if ( result != noErr ) {
+      errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
+      error( RtMidiError::WARNING, errorString_ );
     }
-    messageIndex += packetBytes;
-    bytesLeft -= packetBytes;
-
-    // Send to any destinations that may have connected to us.
-    OSStatus result;
-    if ( data->endpoint ) {
-      result = MIDIReceived( data->endpoint, packetList );
-      if ( result != noErr ) {
-        errorString_ = "RtMidiOut::sendMessage: error sending MIDI to virtual destinations.";
-        error( RtError::WARNING );
-      }
-    }
-
-    // And send to an explicit destination port if we're connected.
-    if ( connected_ ) {
-      result = MIDISend( data->port, data->destinationId, packetList );
-      if ( result != noErr ) {
-        errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port.";
-        error( RtError::WARNING );
-      }
+  }
+
+  // And send to an explicit destination port if we're connected.
+  if ( connected_ ) {
+    result = MIDISend( data->port, data->destinationId, packetList );
+    if ( result != noErr ) {
+      errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";
+      error( RtMidiError::WARNING, errorString_ );
     }
   }
 }
@@ -667,7 +1079,7 @@
 // API information found at:
 //   - http://www.alsa-project.org/documentation.php#Library
 
-#if defined(__LINUX_ALSASEQ__)
+#if defined(__LINUX_ALSA__)
 
 // The ALSA Sequencer API is based on the use of a callback function for
 // MIDI input.
@@ -675,6 +1087,10 @@
 // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
 // time stamps and other assorted fixes!!!
 
+// If you don't need timestamping for incoming MIDI events, define the
+// preprocessor definition AVOID_TIMESTAMPING to save resources
+// associated with the ALSA sequencer queues.
+
 #include <pthread.h>
 #include <sys/time.h>
 
@@ -685,32 +1101,38 @@
 // implementation.
 struct AlsaMidiData {
   snd_seq_t *seq;
+  unsigned int portNum;
   int vport;
   snd_seq_port_subscribe_t *subscription;
   snd_midi_event_t *coder;
   unsigned int bufferSize;
   unsigned char *buffer;
   pthread_t thread;
+  pthread_t dummy_thread_id;
   unsigned long long lastTime;
   int queue_id; // an input queue is needed to get timestamped events
+  int trigger_fds[2];
 };
 
 #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
 
 //*********************************************************************//
 //  API: LINUX ALSA
-//  Class Definitions: RtMidiIn
+//  Class Definitions: MidiInAlsa
 //*********************************************************************//
 
-extern "C" void *alsaMidiHandler( void *ptr )
+static void *alsaMidiHandler( void *ptr )
 {
-  RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (ptr);
+  MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);
   AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);
 
   long nBytes;
   unsigned long long time, lastTime;
   bool continueSysex = false;
-  RtMidiIn::MidiMessage message;
+  bool doDecode = false;
+  MidiInApi::MidiMessage message;
+  int poll_fd_count;
+  struct pollfd *poll_fds;
 
   snd_seq_event_t *ev;
   int result;
@@ -718,72 +1140,93 @@
   result = snd_midi_event_new( 0, &apiData->coder );
   if ( result < 0 ) {
     data->doInput = false;
-    cerr << "\nRtMidiIn::alsaMidiHandler: error initializing MIDI event parser!\n\n";
+    std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";
     return 0;
   }
   unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
   if ( buffer == NULL ) {
     data->doInput = false;
-    cerr << "\nRtMidiIn::alsaMidiHandler: error initializing buffer memory!\n\n";
+    snd_midi_event_free( apiData->coder );
+    apiData->coder = 0;
+    std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";
     return 0;
   }
   snd_midi_event_init( apiData->coder );
   snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
 
+  poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;
+  poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));
+  snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );
+  poll_fds[0].fd = apiData->trigger_fds[0];
+  poll_fds[0].events = POLLIN;
+
   while ( data->doInput ) {
 
     if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
-      // No data pending ... sleep a bit.
-      usleep( 1000 );
+      // No data pending
+      if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {
+        if ( poll_fds[0].revents & POLLIN ) {
+          bool dummy;
+          int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );
+          (void) res;
+        }
+      }
       continue;
     }
 
     // If here, there should be data.
     result = snd_seq_event_input( apiData->seq, &ev );
     if ( result == -ENOSPC ) {
-      cerr << "\nRtMidiIn::alsaMidiHandler: MIDI input buffer overrun!\n\n";
+      std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";
       continue;
     }
     else if ( result <= 0 ) {
-      cerr << "RtMidiIn::alsaMidiHandler: unknown MIDI input error!\n";
+      std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";
+      perror("System reports");
       continue;
     }
 
     // This is a bit weird, but we now have to decode an ALSA MIDI
     // event (back) into MIDI bytes.  We'll ignore non-MIDI types.
-    if ( !continueSysex )
-      message.bytes.clear();
-
+    if ( !continueSysex ) message.bytes.clear();
+
+    doDecode = false;
     switch ( ev->type ) {
 
-		case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+    case SND_SEQ_EVENT_PORT_SUBSCRIBED:
 #if defined(__RTMIDI_DEBUG__)
-      cout << "RtMidiIn::alsaMidiHandler: port connection made!\n";
+      std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";
 #endif
       break;
 
-		case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+    case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
 #if defined(__RTMIDI_DEBUG__)
-      SVDEBUG << "RtMidiIn::alsaMidiHandler: port connection has closed!\n";
-      // FIXME: this is called for all unsubscribe events, even ones
-      //not related to this particular connection.  As it stands, I
-      //see no data provided in the "source" and "dest" fields so
-      //there is nothing we can do about this at this time.
-      // cout << "sender = " << ev->source.client << ", dest = " << ev->dest.port << endl;
+      std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";
+      std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"
+                << (int) ev->data.connect.sender.port
+                << ", dest = " << (int) ev->data.connect.dest.client << ":"
+                << (int) ev->data.connect.dest.port
+                << std::endl;
 #endif
-      //data->doInput = false;
       break;
 
     case SND_SEQ_EVENT_QFRAME: // MIDI time code
-      if ( data->ignoreFlags & 0x02 ) break;
-
-    case SND_SEQ_EVENT_TICK: // MIDI timing tick
-      if ( data->ignoreFlags & 0x02 ) break;
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
 
     case SND_SEQ_EVENT_SENSING: // Active sensing
-      if ( data->ignoreFlags & 0x04 ) break;
-
-		case SND_SEQ_EVENT_SYSEX:
+      if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_SYSEX:
       if ( (data->ignoreFlags & 0x01) ) break;
       if ( ev->data.ext.len > apiData->bufferSize ) {
         apiData->bufferSize = ev->data.ext.len;
@@ -791,84 +1234,128 @@
         buffer = (unsigned char *) malloc( apiData->bufferSize );
         if ( buffer == NULL ) {
           data->doInput = false;
-          cerr << "\nRtMidiIn::alsaMidiHandler: error resizing buffer memory!\n\n";
+          std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";
           break;
         }
       }
+      doDecode = true;
+      break;
 
     default:
+      doDecode = true;
+    }
+
+    if ( doDecode ) {
+
       nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
-      if ( nBytes <= 0 ) {
+      if ( nBytes > 0 ) {
+        // The ALSA sequencer has a maximum buffer size for MIDI sysex
+        // events of 256 bytes.  If a device sends sysex messages larger
+        // than this, they are segmented into 256 byte chunks.  So,
+        // we'll watch for this and concatenate sysex chunks into a
+        // single sysex message if necessary.
+        if ( !continueSysex )
+          message.bytes.assign( buffer, &buffer[nBytes] );
+        else
+          message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
+
+        continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
+        if ( !continueSysex ) {
+
+          // Calculate the time stamp:
+          message.timeStamp = 0.0;
+
+          // Method 1: Use the system time.
+          //(void)gettimeofday(&tv, (struct timezone *)NULL);
+          //time = (tv.tv_sec * 1000000) + tv.tv_usec;
+
+          // Method 2: Use the ALSA sequencer event time data.
+          // (thanks to Pedro Lopez-Cabanillas!).
+          time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
+          lastTime = time;
+          time -= apiData->lastTime;
+          apiData->lastTime = lastTime;
+          if ( data->firstMessage == true )
+            data->firstMessage = false;
+          else
+            message.timeStamp = time * 0.000001;
+        }
+        else {
 #if defined(__RTMIDI_DEBUG__)
-        cerr << "\nRtMidiIn::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
+          std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
 #endif
-        break;
+        }
       }
-
-      // The ALSA sequencer has a maximum buffer size for MIDI sysex
-      // events of 256 bytes.  If a device sends sysex messages larger
-      // than this, they are segmented into 256 byte chunks.  So,
-      // we'll watch for this and concatenate sysex chunks into a
-      // single sysex message if necessary.
-      if ( !continueSysex )
-        message.bytes.assign( buffer, &buffer[nBytes] );
-      else
-        message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
-
-      continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
-      if ( continueSysex )
-        break;
-
-      // Calculate the time stamp:
-      message.timeStamp = 0.0;
-
-      // Method 1: Use the system time.
-      //(void)gettimeofday(&tv, (struct timezone *)NULL);
-      //time = (tv.tv_sec * 1000000) + tv.tv_usec;
-
-      // Method 2: Use the ALSA sequencer event time data.
-      // (thanks to Pedro Lopez-Cabanillas!).
-      time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
-      lastTime = time;
-      time -= apiData->lastTime;
-      apiData->lastTime = lastTime;
-      if ( data->firstMessage == true )
-        data->firstMessage = false;
-      else
-	  message.timeStamp = double(time) * 0.000001;
     }
 
-    snd_seq_free_event(ev);
-    if ( message.bytes.size() == 0 ) continue;
-
-    if ( data->usingCallback && !continueSysex ) {
+    snd_seq_free_event( ev );
+    if ( message.bytes.size() == 0 || continueSysex ) continue;
+
+    if ( data->usingCallback ) {
       RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
       callback( message.timeStamp, &message.bytes, data->userData );
     }
     else {
       // As long as we haven't reached our queue size limit, push the message.
-      if ( data->queueLimit > data->queue.size() )
-        data->queue.push( message );
+      if ( data->queue.size < data->queue.ringSize ) {
+        data->queue.ring[data->queue.back++] = message;
+        if ( data->queue.back == data->queue.ringSize )
+          data->queue.back = 0;
+        data->queue.size++;
+      }
       else
-        cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
+        std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";
     }
   }
 
   if ( buffer ) free( buffer );
   snd_midi_event_free( apiData->coder );
   apiData->coder = 0;
+  apiData->thread = apiData->dummy_thread_id;
   return 0;
 }
 
-void RtMidiIn :: initialize( const std::string& clientName )
+MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInAlsa :: ~MidiInAlsa()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Shutdown the input thread.
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( inputData_.doInput ) {
+    inputData_.doInput = false;
+    int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
+    (void) res;
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+  }
+
+  // Cleanup.
+  close ( data->trigger_fds[0] );
+  close ( data->trigger_fds[1] );
+  if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
+#ifndef AVOID_TIMESTAMPING
+  snd_seq_free_queue( data->seq, data->queue_id );
+#endif
+  snd_seq_close( data->seq );
+  delete data;
+}
+
+void MidiInAlsa :: initialize( const std::string& clientName )
 {
   // Set up the ALSA sequencer client.
   snd_seq_t *seq;
   int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
   if ( result < 0 ) {
-    errorString_ = "RtMidiIn::initialize: error creating ALSA sequencer input client object.";
-    error( RtError::DRIVER_ERROR );
-	}
+    errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
 
   // Set client name.
   snd_seq_set_client_name( seq, clientName.c_str() );
@@ -876,11 +1363,24 @@
   // Save our api-specific connection information.
   AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
   data->seq = seq;
+  data->portNum = -1;
   data->vport = -1;
+  data->subscription = 0;
+  data->dummy_thread_id = pthread_self();
+  data->thread = data->dummy_thread_id;
+  data->trigger_fds[0] = -1;
+  data->trigger_fds[1] = -1;
   apiData_ = (void *) data;
   inputData_.apiData = (void *) data;
 
+   if ( pipe(data->trigger_fds) == -1 ) {
+    errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
   // Create the input queue
+#ifndef AVOID_TIMESTAMPING
   data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
   // Set arbitrary tempo (mm=100) and resolution (240)
   snd_seq_queue_tempo_t *qtempo;
@@ -889,67 +1389,110 @@
   snd_seq_queue_tempo_set_ppq(qtempo, 240);
   snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);
   snd_seq_drain_output(data->seq);
+#endif
 }
 
 // This function is used to count or get the pinfo structure for a given port number.
 unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
 {
-	snd_seq_client_info_t *cinfo;
+  snd_seq_client_info_t *cinfo;
   int client;
   int count = 0;
-	snd_seq_client_info_alloca( &cinfo );
-
-	snd_seq_client_info_set_client( cinfo, -1 );
-	while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
+  snd_seq_client_info_alloca( &cinfo );
+
+  snd_seq_client_info_set_client( cinfo, -1 );
+  while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
     client = snd_seq_client_info_get_client( cinfo );
     if ( client == 0 ) continue;
-		// Reset query info
-		snd_seq_port_info_set_client( pinfo, client );
-		snd_seq_port_info_set_port( pinfo, -1 );
-		while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
+    // Reset query info
+    snd_seq_port_info_set_client( pinfo, client );
+    snd_seq_port_info_set_port( pinfo, -1 );
+    while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
       unsigned int atyp = snd_seq_port_info_get_type( pinfo );
-      if ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) continue;
+      if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&
+        ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) ) continue;
       unsigned int caps = snd_seq_port_info_get_capability( pinfo );
       if ( ( caps & type ) != type ) continue;
       if ( count == portNumber ) return 1;
-      count++;
-		}
-	}
+      ++count;
+    }
+  }
 
   // If a negative portNumber was used, return the port count.
   if ( portNumber < 0 ) return count;
   return 0;
 }
 
-void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName )
+unsigned int MidiInAlsa :: getPortCount()
+{
+  snd_seq_port_info_t *pinfo;
+  snd_seq_port_info_alloca( &pinfo );
+
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
+}
+
+std::string MidiInAlsa :: getPortName( unsigned int portNumber )
+{
+  snd_seq_client_info_t *cinfo;
+  snd_seq_port_info_t *pinfo;
+  snd_seq_client_info_alloca( &cinfo );
+  snd_seq_port_info_alloca( &pinfo );
+
+  std::string stringName;
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
+    int cnum = snd_seq_port_info_get_client( pinfo );
+    snd_seq_get_any_client_info( data->seq, cnum, cinfo );
+    std::ostringstream os;
+    os << snd_seq_client_info_get_name( cinfo );
+    os << " ";                                    // These lines added to make sure devices are listed
+    os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
+    os << ":";
+    os << snd_seq_port_info_get_port( pinfo );
+    stringName = os.str();
+    return stringName;
+  }
+
+  // If we get here, we didn't find a match.
+  errorString_ = "MidiInAlsa::getPortName: error looking for port name!";
+  error( RtMidiError::WARNING, errorString_ );
+  return stringName;
+}
+
+void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   unsigned int nSrc = this->getPortCount();
-  if (nSrc < 1) {
-    errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
-    error( RtError::NO_DEVICES_FOUND );
+  if ( nSrc < 1 ) {
+    errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-	snd_seq_port_info_t *pinfo;
-	snd_seq_port_info_alloca( &pinfo );
-  std::ostringstream ost;
+  snd_seq_port_info_t *src_pinfo;
+  snd_seq_port_info_alloca( &src_pinfo );
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
-    ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+  if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
+    std::ostringstream ost;
+    ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
-
   snd_seq_addr_t sender, receiver;
-  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 );
+  sender.client = snd_seq_port_info_get_client( src_pinfo );
+  sender.port = snd_seq_port_info_get_port( src_pinfo );
+  receiver.client = snd_seq_client_id( data->seq );
+
+  snd_seq_port_info_t *pinfo;
+  snd_seq_port_info_alloca( &pinfo );
   if ( data->vport < 0 ) {
     snd_seq_port_info_set_client( pinfo, 0 );
     snd_seq_port_info_set_port( pinfo, 0 );
@@ -960,33 +1503,48 @@
                                 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
                                 SND_SEQ_PORT_TYPE_APPLICATION );
     snd_seq_port_info_set_midi_channels(pinfo, 16);
+#ifndef AVOID_TIMESTAMPING
     snd_seq_port_info_set_timestamping(pinfo, 1);
     snd_seq_port_info_set_timestamp_real(pinfo, 1);    
     snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
+#endif
     snd_seq_port_info_set_name(pinfo,  portName.c_str() );
     data->vport = snd_seq_create_port(data->seq, pinfo);
   
     if ( data->vport < 0 ) {
-      errorString_ = "RtMidiIn::openPort: ALSA error creating input port.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    data->vport = snd_seq_port_info_get_port(pinfo);
+  }
+
+  receiver.port = data->vport;
+
+  if ( !data->subscription ) {
+    // Make subscription
+    if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
+      errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    snd_seq_port_subscribe_set_sender(data->subscription, &sender);
+    snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
+    if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
+      snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
+      errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
   }
 
-  receiver.port = (unsigned char)data->vport;
-
-  // Make subscription
-  snd_seq_port_subscribe_malloc( &data->subscription );
-  snd_seq_port_subscribe_set_sender(data->subscription, &sender);
-  snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
-  if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
-    errorString_ = "RtMidiIn::openPort: ALSA error making port connection.";
-    error( RtError::DRIVER_ERROR );
-  }
-
   if ( inputData_.doInput == false ) {
     // Start the input queue
+#ifndef AVOID_TIMESTAMPING
     snd_seq_start_queue( data->seq, data->queue_id, NULL );
     snd_seq_drain_output( data->seq );
+#endif
     // Start our MIDI input thread.
     pthread_attr_t attr;
     pthread_attr_init(&attr);
@@ -996,47 +1554,59 @@
     inputData_.doInput = true;
     int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
     pthread_attr_destroy(&attr);
-    if (err) {
+    if ( err ) {
       snd_seq_unsubscribe_port( data->seq, data->subscription );
       snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
       inputData_.doInput = false;
-      errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
-      error( RtError::THREAD_ERROR );
+      errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+      error( RtMidiError::THREAD_ERROR, errorString_ );
+      return;
     }
   }
 
   connected_ = true;
 }
 
-void RtMidiIn :: openVirtualPort( std::string portName )
+void MidiInAlsa :: openVirtualPort( std::string portName )
 {
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
   if ( data->vport < 0 ) {
     snd_seq_port_info_t *pinfo;
     snd_seq_port_info_alloca( &pinfo );
     snd_seq_port_info_set_capability( pinfo,
-				      SND_SEQ_PORT_CAP_WRITE |
-				      SND_SEQ_PORT_CAP_SUBS_WRITE );
+                                      SND_SEQ_PORT_CAP_WRITE |
+                                      SND_SEQ_PORT_CAP_SUBS_WRITE );
     snd_seq_port_info_set_type( pinfo,
-				SND_SEQ_PORT_TYPE_MIDI_GENERIC |
-				SND_SEQ_PORT_TYPE_APPLICATION );
+                                SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+                                SND_SEQ_PORT_TYPE_APPLICATION );
     snd_seq_port_info_set_midi_channels(pinfo, 16);
+#ifndef AVOID_TIMESTAMPING
     snd_seq_port_info_set_timestamping(pinfo, 1);
     snd_seq_port_info_set_timestamp_real(pinfo, 1);    
     snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
+#endif
     snd_seq_port_info_set_name(pinfo, portName.c_str());
     data->vport = snd_seq_create_port(data->seq, pinfo);
 
     if ( data->vport < 0 ) {
-      errorString_ = "RtMidiIn::openVirtualPort: ALSA error creating virtual port.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
+    data->vport = snd_seq_port_info_get_port(pinfo);
   }
 
   if ( inputData_.doInput == false ) {
+    // Wait for old thread to stop, if still running
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+
     // Start the input queue
+#ifndef AVOID_TIMESTAMPING
     snd_seq_start_queue( data->seq, data->queue_id, NULL );
     snd_seq_drain_output( data->seq );
+#endif
     // Start our MIDI input thread.
     pthread_attr_t attr;
     pthread_attr_init(&attr);
@@ -1046,213 +1616,215 @@
     inputData_.doInput = true;
     int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
     pthread_attr_destroy(&attr);
-    if (err) {
-      snd_seq_unsubscribe_port( data->seq, data->subscription );
-      snd_seq_port_subscribe_free( data->subscription );
+    if ( err ) {
+      if ( data->subscription ) {
+        snd_seq_unsubscribe_port( data->seq, data->subscription );
+        snd_seq_port_subscribe_free( data->subscription );
+        data->subscription = 0;
+      }
       inputData_.doInput = false;
-      errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
-      error( RtError::THREAD_ERROR );
+      errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+      error( RtMidiError::THREAD_ERROR, errorString_ );
+      return;
     }
   }
 }
 
-void RtMidiIn :: closePort( void )
+void MidiInAlsa :: closePort( void )
 {
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+
   if ( connected_ ) {
-    AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-    snd_seq_unsubscribe_port( data->seq, data->subscription );
-    snd_seq_port_subscribe_free( data->subscription );
+    if ( data->subscription ) {
+      snd_seq_unsubscribe_port( data->seq, data->subscription );
+      snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
+    }
     // Stop the input queue
+#ifndef AVOID_TIMESTAMPING
     snd_seq_stop_queue( data->seq, data->queue_id, NULL );
     snd_seq_drain_output( data->seq );
+#endif
     connected_ = false;
   }
+
+  // Stop thread to avoid triggering the callback, while the port is intended to be closed
+  if ( inputData_.doInput ) {
+    inputData_.doInput = false;
+    int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
+    (void) res;
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+  }
 }
 
-RtMidiIn :: ~RtMidiIn()
+//*********************************************************************//
+//  API: LINUX ALSA
+//  Class Definitions: MidiOutAlsa
+//*********************************************************************//
+
+MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutAlsa :: ~MidiOutAlsa()
 {
   // Close a connection if it exists.
   closePort();
 
-  // Shutdown the input thread.
+  // Cleanup.
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  if ( inputData_.doInput ) {
-    inputData_.doInput = false;
-    pthread_join( data->thread, NULL );
-  }
-
-  // Cleanup.
   if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
-  snd_seq_free_queue( data->seq, data->queue_id );
+  if ( data->coder ) snd_midi_event_free( data->coder );
+  if ( data->buffer ) free( data->buffer );
   snd_seq_close( data->seq );
   delete data;
 }
 
-unsigned int RtMidiIn :: getPortCount()
+void MidiOutAlsa :: initialize( const std::string& clientName )
 {
-	snd_seq_port_info_t *pinfo;
-	snd_seq_port_info_alloca( &pinfo );
+  // Set up the ALSA sequencer client.
+  snd_seq_t *seq;
+  int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
+  if ( result1 < 0 ) {
+    errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+        }
+
+  // Set client name.
+  snd_seq_set_client_name( seq, clientName.c_str() );
+
+  // Save our api-specific connection information.
+  AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
+  data->seq = seq;
+  data->portNum = -1;
+  data->vport = -1;
+  data->bufferSize = 32;
+  data->coder = 0;
+  data->buffer = 0;
+  int result = snd_midi_event_new( data->bufferSize, &data->coder );
+  if ( result < 0 ) {
+    delete data;
+    errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+  data->buffer = (unsigned char *) malloc( data->bufferSize );
+  if ( data->buffer == NULL ) {
+    delete data;
+    errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+    error( RtMidiError::MEMORY_ERROR, errorString_ );
+    return;
+  }
+  snd_midi_event_init( data->coder );
+  apiData_ = (void *) data;
+}
+
+unsigned int MidiOutAlsa :: getPortCount()
+{
+        snd_seq_port_info_t *pinfo;
+        snd_seq_port_info_alloca( &pinfo );
 
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
+  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
 }
 
-std::string RtMidiIn :: getPortName( unsigned int portNumber )
+std::string MidiOutAlsa :: getPortName( unsigned int portNumber )
 {
   snd_seq_client_info_t *cinfo;
   snd_seq_port_info_t *pinfo;
   snd_seq_client_info_alloca( &cinfo );
   snd_seq_port_info_alloca( &pinfo );
 
-  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
-    int cnum = snd_seq_port_info_get_client( pinfo );
-    snd_seq_get_any_client_info( data->seq, cnum, cinfo );
-    std::ostringstream os;
-    os << snd_seq_client_info_get_name( cinfo );
-    os << ":";
-    os << snd_seq_port_info_get_port( pinfo );
-    std::string stringName = os.str();
-    return stringName;
-  }
-
-  // If we get here, we didn't find a match.
-  errorString_ = "RtMidiIn::getPortName: error looking for port name!";
-  error( RtError::INVALID_PARAMETER );
-  return 0;
-}
-
-//*********************************************************************//
-//  API: LINUX ALSA
-//  Class Definitions: RtMidiOut
-//*********************************************************************//
-
-unsigned int RtMidiOut :: getPortCount()
-{
-	snd_seq_port_info_t *pinfo;
-	snd_seq_port_info_alloca( &pinfo );
-
-  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
-}
-
-std::string RtMidiOut :: getPortName( unsigned int portNumber )
-{
-  snd_seq_client_info_t *cinfo;
-  snd_seq_port_info_t *pinfo;
-  snd_seq_client_info_alloca( &cinfo );
-  snd_seq_port_info_alloca( &pinfo );
-
+  std::string stringName;
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
   if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
     int cnum = snd_seq_port_info_get_client(pinfo);
     snd_seq_get_any_client_info( data->seq, cnum, cinfo );
     std::ostringstream os;
     os << snd_seq_client_info_get_name(cinfo);
+    os << " ";                                    // These lines added to make sure devices are listed
+    os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
     os << ":";
     os << snd_seq_port_info_get_port(pinfo);
-    std::string stringName = os.str();
+    stringName = os.str();
     return stringName;
   }
 
   // If we get here, we didn't find a match.
-  errorString_ = "RtMidiOut::getPortName: error looking for port name!";
-  error( RtError::INVALID_PARAMETER );
-  return 0;
+  errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";
+  error( RtMidiError::WARNING, errorString_ );
+  return stringName;
 }
 
-void RtMidiOut :: initialize( const std::string& clientName )
-{
-  // Set up the ALSA sequencer client.
-  snd_seq_t *seq;
-  int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
-  if ( result < 0 ) {
-    errorString_ = "RtMidiOut::initialize: error creating ALSA sequencer client object.";
-    error( RtError::DRIVER_ERROR );
-	}
-
-  // Set client name.
-  snd_seq_set_client_name( seq, clientName.c_str() );
-
-  // Save our api-specific connection information.
-  AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
-  data->seq = seq;
-  data->vport = -1;
-  data->bufferSize = 32;
-  data->coder = 0;
-  data->buffer = 0;
-  result = snd_midi_event_new( data->bufferSize, &data->coder );
-  if ( result < 0 ) {
-    delete data;
-    errorString_ = "RtMidiOut::initialize: error initializing MIDI event parser!\n\n";
-    error( RtError::DRIVER_ERROR );
-  }
-  data->buffer = (unsigned char *) malloc( data->bufferSize );
-  if ( data->buffer == NULL ) {
-    delete data;
-    errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n";
-    error( RtError::MEMORY_ERROR );
-  }
-  snd_midi_event_init( data->coder );
-  apiData_ = (void *) data;
-}
-
-void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName )
+void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   unsigned int nSrc = this->getPortCount();
   if (nSrc < 1) {
-    errorString_ = "RtMidiOut::openPort: no MIDI output sources found!";
-    error( RtError::NO_DEVICES_FOUND );
+    errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-	snd_seq_port_info_t *pinfo;
-	snd_seq_port_info_alloca( &pinfo );
-  std::ostringstream ost;
+        snd_seq_port_info_t *pinfo;
+        snd_seq_port_info_alloca( &pinfo );
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
   if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
-    ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    std::ostringstream ost;
+    ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
   snd_seq_addr_t sender, receiver;
-  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 );
+  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 );
 
   if ( data->vport < 0 ) {
     data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
                                               SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
-                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC );
+                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
     if ( data->vport < 0 ) {
-      errorString_ = "RtMidiOut::openPort: ALSA error creating output port.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
   }
 
-  sender.port = (unsigned char)data->vport;
+  sender.port = data->vport;
 
   // Make subscription
-  snd_seq_port_subscribe_malloc( &data->subscription );
+  if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
+    snd_seq_port_subscribe_free( data->subscription );
+    errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
   snd_seq_port_subscribe_set_sender(data->subscription, &sender);
   snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
   snd_seq_port_subscribe_set_time_update(data->subscription, 1);
   snd_seq_port_subscribe_set_time_real(data->subscription, 1);
   if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
-    errorString_ = "RtMidiOut::openPort: ALSA error making port connection.";
-    error( RtError::DRIVER_ERROR );
+    snd_seq_port_subscribe_free( data->subscription );
+    errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   connected_ = true;
 }
 
-void RtMidiOut :: closePort( void )
+void MidiOutAlsa :: closePort( void )
 {
   if ( connected_ ) {
     AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
@@ -1262,73 +1834,62 @@
   }
 }
 
-void RtMidiOut :: openVirtualPort( std::string portName )
+void MidiOutAlsa :: openVirtualPort( std::string portName )
 {
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
   if ( data->vport < 0 ) {
     data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
                                               SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
-                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC );
+                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
 
     if ( data->vport < 0 ) {
-      errorString_ = "RtMidiOut::openVirtualPort: ALSA error creating virtual port.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
     }
   }
 }
 
-RtMidiOut :: ~RtMidiOut()
-{
-  // Close a connection if it exists.
-  closePort();
-
-  // Cleanup.
-  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
-  if ( data->coder ) snd_midi_event_free( data->coder );
-  if ( data->buffer ) free( data->buffer );
-  snd_seq_close( data->seq );
-  delete data;
-}
-
-void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
+void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message )
 {
   int result;
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  unsigned int nBytes = (unsigned int) message->size();
+  unsigned int nBytes = message->size();
   if ( nBytes > data->bufferSize ) {
     data->bufferSize = nBytes;
     result = snd_midi_event_resize_buffer ( data->coder, nBytes);
     if ( result != 0 ) {
-      errorString_ = "RtMidiOut::sendMessage: ALSA error resizing MIDI event buffer.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
     free (data->buffer);
     data->buffer = (unsigned char *) malloc( data->bufferSize );
     if ( data->buffer == NULL ) {
-    errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n";
-    error( RtError::MEMORY_ERROR );
+    errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+    error( RtMidiError::MEMORY_ERROR, errorString_ );
+    return;
     }
   }
 
   snd_seq_event_t ev;
   snd_seq_ev_clear(&ev);
-  snd_seq_ev_set_source(&ev, (unsigned char) data->vport);
+  snd_seq_ev_set_source(&ev, 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 = (int) snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &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 );
   if ( result < (int)nBytes ) {
-    errorString_ = "RtMidiOut::sendMessage: event parsing error!";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   // Send the event.
   result = snd_seq_event_output(data->seq, &ev);
   if ( result < 0 ) {
-    errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port.";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
   }
   snd_seq_drain_output(data->seq);
 }
@@ -1337,406 +1898,6 @@
 
 
 //*********************************************************************//
-//  API: IRIX MD
-//*********************************************************************//
-
-// API information gleamed from:
-//   http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?cmd=getdoc&coll=0650&db=man&fname=3%20mdIntro
-
-// If the Makefile doesn't work, try the following:
-// CC -o midiinfo -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiinfo.cpp -lpthread -lmd
-// CC -o midiout -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiout.cpp -lpthread -lmd
-// CC -o qmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp qmidiin.cpp -lpthread -lmd
-// CC -o cmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp cmidiin.cpp -lpthread -lmd
-
-#if defined(__IRIX_MD__)
-
-#include <pthread.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-// Irix MIDI header file.
-#include <dmedia/midi.h>
-
-// A structure to hold variables related to the IRIX API
-// implementation.
-struct IrixMidiData {
-  MDport port;
-  pthread_t thread;
-};
-
-//*********************************************************************//
-//  API: IRIX
-//  Class Definitions: RtMidiIn
-//*********************************************************************//
-
-extern "C" void *irixMidiHandler( void *ptr )
-{
-  RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (ptr);
-  IrixMidiData *apiData = static_cast<IrixMidiData *> (data->apiData);
-
-  bool continueSysex = false;
-  unsigned char status;
-  unsigned short size;
-  MDevent event;
-  int fd = mdGetFd( apiData->port );
-  if ( fd < 0 ) {
-    data->doInput = false;
-    cerr << "\nRtMidiIn::irixMidiHandler: error getting port descriptor!\n\n";
-    return 0;
-  }
-
-  fd_set mask, rmask;
-  FD_ZERO( &mask );
-  FD_SET( fd, &mask );
-  struct timeval timeout = {0, 0};
-  RtMidiIn::MidiMessage message;
-  int result;
-
-  while ( data->doInput ) {
-
-    rmask = mask;
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 0;
-    if ( select( fd+1, &rmask, NULL, NULL, &timeout ) <= 0 ) {
-      // No data pending ... sleep a bit.
-      usleep( 1000 );
-      continue;
-    }
-
-    // If here, there should be data.
-    result = mdReceive( apiData->port, &event, 1);
-    if ( result <= 0 ) {
-      cerr << "\nRtMidiIn::irixMidiHandler: MIDI input read error!\n\n";
-      continue;
-    }
-
-    message.timeStamp = event.stamp * 0.000000001;
-
-    size = 0;
-    status = event.msg[0];
-    if ( !(status & 0x80) ) continue;
-    if ( status == 0xF0 ) {
-      // Sysex message ... can be segmented across multiple messages.
-      if ( !(data->ignoreFlags & 0x01) ) {
-        if ( continueSysex ) {
-          // We have a continuing, segmented sysex message.  Append
-          // the new bytes to our existing message.
-          for ( int i=0; i<event.msglen; i++ )
-            message.bytes.push_back( event.sysexmsg[i] );
-          if ( event.sysexmsg[event.msglen-1] == 0xF7 ) continueSysex = false;
-          if ( !continueSysex ) {
-            // If not a continuing sysex message, invoke the user callback function or queue the message.
-            if ( data->usingCallback && message.bytes.size() > 0 ) {
-              RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
-              callback( message.timeStamp, &message.bytes, data->userData );
-            }
-            else {
-              // As long as we haven't reached our queue size limit, push the message.
-              if ( data->queueLimit > data->queue.size() )
-                data->queue.push( message );
-              else
-                cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
-            }
-            message.bytes.clear();
-          }
-        }
-      }
-      mdFree( NULL );
-      continue;
-    }
-    else if ( status < 0xC0 ) size = 3;
-    else if ( status < 0xE0 ) size = 2;
-    else if ( status < 0xF0 ) size = 3;
-    else if ( status < 0xF3 ) {
-      if ( status == 0xF1 && !(data->ignoreFlags & 0x02) ) {
-        // A MIDI time code message and we're not ignoring it.
-        size = 3;
-      }
-    }
-    else if ( status == 0xF3 ) size = 2;
-    else if ( status == 0xF8 ) {
-      if ( !(data->ignoreFlags & 0x02) ) {
-        // A MIDI timing tick message and we're not ignoring it.
-        size = 1;
-      }
-    }
-    else if ( status == 0xFE ) { // MIDI active sensing
-      if ( !(data->ignoreFlags & 0x04) )
-        size = 1;
-    }
-    else size = 1;
-
-    // Copy the MIDI data to our vector.
-    if ( size ) {
-      message.bytes.assign( &event.msg[0], &event.msg[size] );
-      // Invoke the user callback function or queue the message.
-      if ( data->usingCallback ) {
-        RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
-        callback( message.timeStamp, &message.bytes, data->userData );
-      }
-      else {
-        // As long as we haven't reached our queue size limit, push the message.
-        if ( data->queueLimit > data->queue.size() )
-          data->queue.push( message );
-        else
-          cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
-      }
-      message.bytes.clear();
-    }
-  }
-
-  return 0;
-}
-
-void RtMidiIn :: initialize( const std::string& /*clientName*/ )
-{
-  // Initialize the Irix MIDI system.  At the moment, we will not
-  // worry about a return value of zero (ports) because there is a
-  // chance the user could plug something in after instantiation.
-  int nPorts = mdInit();
-
-  // Create our api-specific connection information.
-  IrixMidiData *data = (IrixMidiData *) new IrixMidiData;
-  apiData_ = (void *) data;
-  inputData_.apiData = (void *) data;
-}
-
-void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
-{
-  if ( connected_ ) {
-    errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
-    return;
-  }
-
-  int nPorts = mdInit();
-  if (nPorts < 1) {
-    errorString_ = "RtMidiIn::openPort: no Irix MIDI input sources found!";
-    error( RtError::NO_DEVICES_FOUND );
-  }
-
-  std::ostringstream ost;
-  if ( portNumber >= nPorts ) {
-    ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-  data->port = mdOpenInPort( mdGetName(portNumber) );
-  if ( data->port == NULL ) {
-    ost << "RtMidiIn::openPort: Irix error opening the port (" << portNumber << ").";
-    errorString_ = ost.str();
-    error( RtError::DRIVER_ERROR );
-  }
-  mdSetStampMode(data->port, MD_DELTASTAMP);
-
-  // Start our MIDI input thread.
-  pthread_attr_t attr;
-  pthread_attr_init(&attr);
-  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
-  pthread_attr_setschedpolicy(&attr, SCHED_RR);
-
-  inputData_.doInput = true;
-  int err = pthread_create(&data->thread, &attr, irixMidiHandler, &inputData_);
-  pthread_attr_destroy(&attr);
-  if (err) {
-    mdClosePort( data->port );
-    inputData_.doInput = false;
-    errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
-    error( RtError::THREAD_ERROR );
-  }
-
-  connected_ = true;
-}
-
-void RtMidiIn :: openVirtualPort( std::string portName )
-{
-  // This function cannot be implemented for the Irix MIDI API.
-  errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Irix MIDI API!";
-  error( RtError::WARNING );
-}
-
-void RtMidiIn :: closePort( void )
-{
-  if ( connected_ ) {
-    IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-    mdClosePort( data->port );
-    connected_ = false;
-
-    // Shutdown the input thread.
-    inputData_.doInput = false;
-    pthread_join( data->thread, NULL );
-  }
-}
-
-RtMidiIn :: ~RtMidiIn()
-{
-  // Close a connection if it exists.
-  closePort();
-
-  // Cleanup.
-  IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-  delete data;
-}
-
-unsigned int RtMidiIn :: getPortCount()
-{
-  int nPorts = mdInit();
-  if ( nPorts >= 0 ) return nPorts;
-  else return 0;
-}
-
-std::string RtMidiIn :: getPortName( unsigned int portNumber )
-{
-  int nPorts = mdInit();
-
-  std::ostringstream ost;
-  if ( portNumber >= nPorts ) {
-    ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  std::string stringName = std::string( mdGetName( portNumber ) );
-  return stringName;
-}
-
-//*********************************************************************//
-//  API: IRIX MD
-//  Class Definitions: RtMidiOut
-//*********************************************************************//
-
-unsigned int RtMidiOut :: getPortCount()
-{
-  int nPorts = mdInit();
-  if ( nPorts >= 0 ) return nPorts;
-  else return 0;
-}
-
-std::string RtMidiOut :: getPortName( unsigned int portNumber )
-{
-  int nPorts = mdInit();
-
-  std::ostringstream ost;
-  if ( portNumber >= nPorts ) {
-    ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  std::string stringName = std::string( mdGetName( portNumber ) );
-  return stringName;
-}
-
-void RtMidiOut :: initialize( const std::string& /*clientName*/ )
-{
-  // Initialize the Irix MIDI system.  At the moment, we will not
-  // worry about a return value of zero (ports) because there is a
-  // chance the user could plug something in after instantiation.
-  int nPorts = mdInit();
-
-  // Create our api-specific connection information.
-  IrixMidiData *data = (IrixMidiData *) new IrixMidiData;
-  apiData_ = (void *) data;
-}
-
-void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
-{
-  if ( connected_ ) {
-    errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
-    return;
-  }
-
-  int nPorts = mdInit();
-  if (nPorts < 1) {
-    errorString_ = "RtMidiOut::openPort: no Irix MIDI output sources found!";
-    error( RtError::NO_DEVICES_FOUND );
-  }
-
-  std::ostringstream ost;
-  if ( portNumber >= nPorts ) {
-    ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-  data->port = mdOpenOutPort( mdGetName(portNumber) );
-  if ( data->port == NULL ) {
-    ost << "RtMidiOut::openPort: Irix error opening the port (" << portNumber << ").";
-    errorString_ = ost.str();
-    error( RtError::DRIVER_ERROR );
-  }
-  mdSetStampMode(data->port, MD_NOSTAMP);
-
-  connected_ = true;
-}
-
-void RtMidiOut :: closePort( void )
-{
-  if ( connected_ ) {
-    IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-    mdClosePort( data->port );
-    connected_ = false;
-  }
-}
-
-void RtMidiOut :: openVirtualPort( std::string portName )
-{
-  // This function cannot be implemented for the Irix MIDI API.
-  errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Irix MIDI API!";
-  error( RtError::WARNING );
-}
-
-RtMidiOut :: ~RtMidiOut()
-{
-  // Close a connection if it exists.
-  closePort();
-
-  // Cleanup.
-  IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-  delete data;
-}
-
-void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
-{
-  int result;
-  MDevent event;
-  IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
-  char *buffer = 0;
-
-  unsigned int nBytes = message->size();
-  if ( nBytes == 0 ) return;
-  event.stamp = 0;
-  if ( message->at(0) == 0xF0 ) {
-    if ( nBytes < 3 ) return; // check for bogus sysex
-    event.msg[0] = 0xF0;
-    event.msglen = nBytes;
-    buffer = (char *) malloc( nBytes );
-    for ( int i=0; i<nBytes; i++ ) buffer[i] = message->at(i);
-    event.sysexmsg = buffer;
-  }
-  else {
-    for ( int i=0; i<nBytes; i++ )
-      event.msg[i] = message->at(i);
-  }
-
-  // Send the event.
-  result = mdSend( data->port, &event, 1 );
-  if ( buffer ) free( buffer );
-  if ( result < 1 ) {
-    errorString_ = "RtMidiOut::sendMessage: IRIX error sending MIDI message!";
-    error( RtError::WARNING );
-    return;
-  }
-}
-
-#endif // __IRIX_MD__
-
-//*********************************************************************//
 //  API: Windows Multimedia Library (MM)
 //*********************************************************************//
 
@@ -1755,38 +1916,42 @@
 #include <windows.h>
 #include <mmsystem.h>
 
+#define  RT_SYSEX_BUFFER_SIZE 1024
+#define  RT_SYSEX_BUFFER_COUNT 4
+
 // A structure to hold variables related to the CoreMIDI API
 // implementation.
 struct WinMidiData {
   HMIDIIN inHandle;    // Handle to Midi Input Device
   HMIDIOUT outHandle;  // Handle to Midi Output Device
   DWORD lastTime;
-  RtMidiIn::MidiMessage message;
-  LPMIDIHDR sysexBuffer;
+  MidiInApi::MidiMessage message;
+  LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];
+  CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
 };
 
-#define  RT_SYSEX_BUFFER_SIZE 1024
-
 //*********************************************************************//
 //  API: Windows MM
-//  Class Definitions: RtMidiIn
+//  Class Definitions: MidiInWinMM
 //*********************************************************************//
 
-static void CALLBACK midiInputCallback( HMIDIOUT hmin,
+static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,
                                         UINT inputStatus, 
-                                        DWORD instancePtr,
-                                        DWORD midiMessage,
+                                        DWORD_PTR instancePtr,
+                                        DWORD_PTR midiMessage,
                                         DWORD timestamp )
 {
-  if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA ) return;
-
-  //RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (instancePtr);
-  RtMidiIn::RtMidiInData *data = (RtMidiIn::RtMidiInData *)instancePtr;
+  if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;
+
+  //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);
+  MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;
   WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);
 
   // Calculate time stamp.
-  apiData->message.timeStamp = 0.0;
-  if ( data->firstMessage == true ) data->firstMessage = false;
+  if ( data->firstMessage == true ) {
+    apiData->message.timeStamp = 0.0;
+    data->firstMessage = false;
+  }
   else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
   apiData->lastTime = timestamp;
 
@@ -1801,11 +1966,11 @@
     if ( status < 0xC0 ) nBytes = 3;
     else if ( status < 0xE0 ) nBytes = 2;
     else if ( status < 0xF0 ) nBytes = 3;
-    else if ( status < 0xF3 ) {
-      // A MIDI time code message and we're ignoring it.
-      if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) return;
-      nBytes = 3;
+    else if ( status == 0xF1 ) {
+      if ( data->ignoreFlags & 0x02 ) return;
+      else nBytes = 2;
     }
+    else if ( status == 0xF2 ) nBytes = 3;
     else if ( status == 0xF3 ) nBytes = 2;
     else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {
       // A MIDI timing tick message and we're ignoring it.
@@ -1818,13 +1983,13 @@
 
     // Copy bytes to our MIDI message.
     unsigned char *ptr = (unsigned char *) &midiMessage;
-    for ( int i=0; i<nBytes; i++ ) apiData->message.bytes.push_back( *ptr++ );
+    for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );
   }
-  else { // Sysex message ( MIM_LONGDATA )
+  else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
     MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 
-    if ( !( data->ignoreFlags & 0x01 ) ) {  
+    if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {  
       // Sysex message and we're not ignoring it
-      for ( int i=0; i<(int)sysex->dwBytesRecorded; i++ )
+      for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )
         apiData->message.bytes.push_back( sysex->lpData[i] );
     }
 
@@ -1836,11 +2001,13 @@
     // buffer when an application closes and in this case, we should
     // avoid requeueing it, else the computer suddenly reboots after
     // one or two minutes.
-    if ( apiData->sysexBuffer->dwBytesRecorded > 0 ) {
+    if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {
       //if ( sysex->dwBytesRecorded > 0 ) {
-      MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer, sizeof(MIDIHDR) );
+      EnterCriticalSection( &(apiData->_mutex) );
+      MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );
+      LeaveCriticalSection( &(apiData->_mutex) );
       if ( result != MMSYSERR_NOERROR )
-        cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
+        std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
 
       if ( data->ignoreFlags & 0x01 ) return;
     }
@@ -1853,24 +2020,45 @@
   }
   else {
     // As long as we haven't reached our queue size limit, push the message.
-    if ( data->queueLimit > data->queue.size() )
-      data->queue.push( apiData->message );
+    if ( data->queue.size < data->queue.ringSize ) {
+      data->queue.ring[data->queue.back++] = apiData->message;
+      if ( data->queue.back == data->queue.ringSize )
+        data->queue.back = 0;
+      data->queue.size++;
+    }
     else
-      cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
+      std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
   }
 
   // Clear the vector for the next input message.
   apiData->message.bytes.clear();
 }
 
-void RtMidiIn :: initialize( const std::string& /*clientName*/ )
+MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInWinMM :: ~MidiInWinMM()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  DeleteCriticalSection( &(data->_mutex) );
+
+  // Cleanup.
+  delete data;
+}
+
+void MidiInWinMM :: initialize( const std::string& /*clientName*/ )
 {
   // We'll issue a warning here if no devices are available but not
   // throw an error since the user can plugin something later.
   unsigned int nDevices = midiInGetNumDevs();
   if ( nDevices == 0 ) {
-    errorString_ = "RtMidiIn::initialize: no MIDI input devices currently available.";
-    error( RtError::WARNING );
+    errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";
+    error( RtMidiError::WARNING, errorString_ );
   }
 
   // Save our api-specific connection information.
@@ -1878,100 +2066,168 @@
   apiData_ = (void *) data;
   inputData_.apiData = (void *) data;
   data->message.bytes.clear();  // needs to be empty for first input message
+
+  if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) {
+    errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";
+    error( RtMidiError::WARNING, errorString_ );
+  }
 }
 
-void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   unsigned int nDevices = midiInGetNumDevs();
   if (nDevices == 0) {
-    errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
-    error( RtError::NO_DEVICES_FOUND );
+    errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-  std::ostringstream ost;
   if ( portNumber >= nDevices ) {
-    ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    std::ostringstream ost;
+    ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
   WinMidiData *data = static_cast<WinMidiData *> (apiData_);
   MMRESULT result = midiInOpen( &data->inHandle,
                                 portNumber,
-                                (DWORD)&midiInputCallback,
-                                (DWORD)&inputData_,
+                                (DWORD_PTR)&midiInputCallback,
+                                (DWORD_PTR)&inputData_,
                                 CALLBACK_FUNCTION );
   if ( result != MMSYSERR_NOERROR ) {
-    errorString_ = "RtMidiIn::openPort: error creating Windows MM MIDI input port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
-  // Allocate and init the sysex buffer.
-  data->sysexBuffer = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
-  data->sysexBuffer->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];
-  data->sysexBuffer->dwBufferLength = RT_SYSEX_BUFFER_SIZE;
-  data->sysexBuffer->dwFlags = 0;
-
-  result = midiInPrepareHeader( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) );
-  if ( result != MMSYSERR_NOERROR ) {
-    midiInClose( data->inHandle );
-    errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
-    error( RtError::DRIVER_ERROR );
-  }
-
-  // Register the buffer.
-  result = midiInAddBuffer( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) );
-  if ( result != MMSYSERR_NOERROR ) {
-    midiInClose( data->inHandle );
-    errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (AddBuffer).";
-    error( RtError::DRIVER_ERROR );
+  // Allocate and init the sysex buffers.
+  for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
+    data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
+    data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];
+    data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;
+    data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
+    data->sysexBuffer[i]->dwFlags = 0;
+
+    result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+    if ( result != MMSYSERR_NOERROR ) {
+      midiInClose( data->inHandle );
+      errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+
+    // Register the buffer.
+    result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+    if ( result != MMSYSERR_NOERROR ) {
+      midiInClose( data->inHandle );
+      errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
   }
 
   result = midiInStart( data->inHandle );
   if ( result != MMSYSERR_NOERROR ) {
     midiInClose( data->inHandle );
-    errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   connected_ = true;
 }
 
-void RtMidiIn :: openVirtualPort( std::string portName )
+void MidiInWinMM :: openVirtualPort( std::string /*portName*/ )
 {
   // This function cannot be implemented for the Windows MM MIDI API.
-  errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
-  error( RtError::WARNING );
+  errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+  error( RtMidiError::WARNING, errorString_ );
 }
 
-void RtMidiIn :: closePort( void )
+void MidiInWinMM :: closePort( void )
 {
   if ( connected_ ) {
     WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+    EnterCriticalSection( &(data->_mutex) );
     midiInReset( data->inHandle );
     midiInStop( data->inHandle );
 
-    int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer, sizeof(MIDIHDR));
-    delete [] data->sysexBuffer->lpData;
-    delete [] data->sysexBuffer;
-    if ( result != MMSYSERR_NOERROR ) {
-      midiInClose( data->inHandle );
-      errorString_ = "RtMidiIn::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
-      error( RtError::DRIVER_ERROR );
+    for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
+      int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));
+      delete [] data->sysexBuffer[i]->lpData;
+      delete [] data->sysexBuffer[i];
+      if ( result != MMSYSERR_NOERROR ) {
+        midiInClose( data->inHandle );
+        errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
+        error( RtMidiError::DRIVER_ERROR, errorString_ );
+        return;
+      }
     }
 
     midiInClose( data->inHandle );
     connected_ = false;
+    LeaveCriticalSection( &(data->_mutex) );
   }
 }
 
-RtMidiIn :: ~RtMidiIn()
+unsigned int MidiInWinMM :: getPortCount()
+{
+  return midiInGetNumDevs();
+}
+
+std::string MidiInWinMM :: getPortName( unsigned int portNumber )
+{
+  std::string stringName;
+  unsigned int nDevices = midiInGetNumDevs();
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  MIDIINCAPS deviceCaps;
+  midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
+
+#if defined( UNICODE ) || defined( _UNICODE )
+  int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
+  stringName.assign( length, 0 );
+  length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
+#else
+  stringName = std::string( deviceCaps.szPname );
+#endif
+
+  // Next lines added to add the portNumber to the name so that 
+  // the device's names are sure to be listed with individual names
+  // even when they have the same brand name
+  std::ostringstream os;
+  os << " ";
+  os << portNumber;
+  stringName += os.str();
+
+  return stringName;
+}
+
+//*********************************************************************//
+//  API: Windows MM
+//  Class Definitions: MidiOutWinMM
+//*********************************************************************//
+
+MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutWinMM :: ~MidiOutWinMM()
 {
   // Close a connection if it exists.
   closePort();
@@ -1981,77 +2237,14 @@
   delete data;
 }
 
-unsigned int RtMidiIn :: getPortCount()
-{
-  return midiInGetNumDevs();
-}
-
-std::string RtMidiIn :: getPortName( unsigned int portNumber )
-{
-  unsigned int nDevices = midiInGetNumDevs();
-  if ( portNumber >= nDevices ) {
-    std::ostringstream ost;
-    ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  MIDIINCAPS deviceCaps;
-  midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
-
-  // For some reason, we need to copy character by character with
-  // UNICODE (thanks to Eduardo Coutinho!).
-  //std::string stringName = std::string( deviceCaps.szPname );
-  char nameString[MAXPNAMELEN];
-  for( int i=0; i<MAXPNAMELEN; i++ )
-    nameString[i] = (char)( deviceCaps.szPname[i] );
-
-  std::string stringName( nameString );
-  return stringName;
-}
-
-//*********************************************************************//
-//  API: Windows MM
-//  Class Definitions: RtMidiOut
-//*********************************************************************//
-
-unsigned int RtMidiOut :: getPortCount()
-{
-  return midiOutGetNumDevs();
-}
-
-std::string RtMidiOut :: getPortName( unsigned int portNumber )
-{
-  unsigned int nDevices = midiOutGetNumDevs();
-  if ( portNumber >= nDevices ) {
-    std::ostringstream ost;
-    ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
-    errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
-  }
-
-  MIDIOUTCAPS deviceCaps;
-  midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));
-
-  // For some reason, we need to copy character by character with
-  // UNICODE (thanks to Eduardo Coutinho!).
-  //std::string stringName = std::string( deviceCaps.szPname );
-  char nameString[MAXPNAMELEN];
-  for( int i=0; i<MAXPNAMELEN; i++ )
-    nameString[i] = (char)( deviceCaps.szPname[i] );
-
-  std::string stringName( nameString );
-  return stringName;
-}
-
-void RtMidiOut :: initialize( const std::string& /*clientName*/ )
+void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )
 {
   // We'll issue a warning here if no devices are available but not
   // throw an error since the user can plug something in later.
   unsigned int nDevices = midiOutGetNumDevs();
   if ( nDevices == 0 ) {
-    errorString_ = "RtMidiOut::initialize: no MIDI output devices currently available.";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";
+    error( RtMidiError::WARNING, errorString_ );
   }
 
   // Save our api-specific connection information.
@@ -2059,25 +2252,66 @@
   apiData_ = (void *) data;
 }
 
-void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+unsigned int MidiOutWinMM :: getPortCount()
+{
+  return midiOutGetNumDevs();
+}
+
+std::string MidiOutWinMM :: getPortName( unsigned int portNumber )
+{
+  std::string stringName;
+  unsigned int nDevices = midiOutGetNumDevs();
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  MIDIOUTCAPS deviceCaps;
+  midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));
+
+#if defined( UNICODE ) || defined( _UNICODE )
+  int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
+  stringName.assign( length, 0 );
+  length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
+#else
+  stringName = std::string( deviceCaps.szPname );
+#endif
+
+  // Next lines added to add the portNumber to the name so that 
+  // the device's names are sure to be listed with individual names
+  // even when they have the same brand name
+  std::ostringstream os;
+  os << " ";
+  os << portNumber;
+  stringName += os.str();
+
+  return stringName;
+}
+
+void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
 {
   if ( connected_ ) {
-    errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
   unsigned int nDevices = midiOutGetNumDevs();
   if (nDevices < 1) {
-    errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!";
-    error( RtError::NO_DEVICES_FOUND );
+    errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
   }
 
-  std::ostringstream ost;
   if ( portNumber >= nDevices ) {
-    ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    std::ostringstream ost;
+    ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
     errorString_ = ost.str();
-    error( RtError::INVALID_PARAMETER );
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
   }
 
   WinMidiData *data = static_cast<WinMidiData *> (apiData_);
@@ -2087,14 +2321,15 @@
                                  (DWORD)NULL,
                                  CALLBACK_NULL );
   if ( result != MMSYSERR_NOERROR ) {
-    errorString_ = "RtMidiOut::openPort: error creating Windows MM MIDI output port.";
-    error( RtError::DRIVER_ERROR );
+    errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
   }
 
   connected_ = true;
 }
 
-void RtMidiOut :: closePort( void )
+void MidiOutWinMM :: closePort( void )
 {
   if ( connected_ ) {
     WinMidiData *data = static_cast<WinMidiData *> (apiData_);
@@ -2104,29 +2339,21 @@
   }
 }
 
-void RtMidiOut :: openVirtualPort( std::string portName )
+void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ )
 {
   // This function cannot be implemented for the Windows MM MIDI API.
-  errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
-  error( RtError::WARNING );
+  errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+  error( RtMidiError::WARNING, errorString_ );
 }
 
-RtMidiOut :: ~RtMidiOut()
+void MidiOutWinMM :: sendMessage( std::vector<unsigned char> *message )
 {
-  // Close a connection if it exists.
-  closePort();
-
-  // Cleanup.
-  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
-  delete data;
-}
-
-void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
-{
-  unsigned int nBytes = message->size();
+  if ( !connected_ ) return;
+
+  unsigned int nBytes = static_cast<unsigned int>(message->size());
   if ( nBytes == 0 ) {
-    errorString_ = "RtMidiOut::sendMessage: message argument is empty!";
-    error( RtError::WARNING );
+    errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";
+    error( RtMidiError::WARNING, errorString_ );
     return;
   }
 
@@ -2137,12 +2364,13 @@
     // Allocate buffer for sysex data.
     char *buffer = (char *) malloc( nBytes );
     if ( buffer == NULL ) {
-      errorString_ = "RtMidiOut::sendMessage: error allocating sysex message memory!";
-      error( RtError::MEMORY_ERROR );
+      errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";
+      error( RtMidiError::MEMORY_ERROR, errorString_ );
+      return;
     }
 
     // Copy data to buffer.
-    for ( unsigned int i=0; i<nBytes; i++ ) buffer[i] = message->at(i);
+    for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message->at(i);
 
     // Create and prepare MIDIHDR structure.
     MIDIHDR sysex;
@@ -2152,116 +2380,465 @@
     result = midiOutPrepareHeader( data->outHandle,  &sysex, sizeof(MIDIHDR) ); 
     if ( result != MMSYSERR_NOERROR ) {
       free( buffer );
-      errorString_ = "RtMidiOut::sendMessage: error preparing sysex header.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
 
     // Send the message.
     result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) );
     if ( result != MMSYSERR_NOERROR ) {
       free( buffer );
-      errorString_ = "RtMidiOut::sendMessage: error sending sysex message.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
     }
 
     // Unprepare the buffer and MIDIHDR.
     while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 );
     free( buffer );
-
   }
   else { // Channel or system message.
 
     // Make sure the message size isn't too big.
     if ( nBytes > 3 ) {
-      errorString_ = "RtMidiOut::sendMessage: message size is greater than 3 bytes (and not sysex)!";
-      error( RtError::WARNING );
+      errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";
+      error( RtMidiError::WARNING, errorString_ );
       return;
     }
 
     // Pack MIDI bytes into double word.
     DWORD packet;
     unsigned char *ptr = (unsigned char *) &packet;
-    for ( unsigned int i=0; i<nBytes; i++ ) {
+    for ( unsigned int i=0; i<nBytes; ++i ) {
       *ptr = message->at(i);
-      ptr++;
+      ++ptr;
     }
 
     // Send the message immediately.
     result = midiOutShortMsg( data->outHandle, packet );
     if ( result != MMSYSERR_NOERROR ) {
-      errorString_ = "RtMidiOut::sendMessage: error sending MIDI message.";
-      error( RtError::DRIVER_ERROR );
+      errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
     }
   }
 }
 
 #endif  // __WINDOWS_MM__
 
-#ifdef __RTMIDI_DUMMY_ONLY__
-
-void RtMidiIn :: initialize( const std::string& /*clientName*/ )
+
+//*********************************************************************//
+//  API: UNIX JACK
+//
+//  Written primarily by Alexander Svetalkin, with updates for delta
+//  time by Gary Scavone, April 2011.
+//
+//  *********************************************************************//
+
+#if defined(__UNIX_JACK__)
+
+// JACK header files
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <jack/ringbuffer.h>
+
+#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer
+
+struct JackMidiData {
+  jack_client_t *client;
+  jack_port_t *port;
+  jack_ringbuffer_t *buffSize;
+  jack_ringbuffer_t *buffMessage;
+  jack_time_t lastTime;
+  MidiInApi :: RtMidiInData *rtMidiIn;
+  };
+
+//*********************************************************************//
+//  API: JACK
+//  Class Definitions: MidiInJack
+//*********************************************************************//
+
+static int jackProcessIn( jack_nframes_t nframes, void *arg )
 {
+  JackMidiData *jData = (JackMidiData *) arg;
+  MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;
+  jack_midi_event_t event;
+  jack_time_t time;
+
+  // Is port created?
+  if ( jData->port == NULL ) return 0;
+  void *buff = jack_port_get_buffer( jData->port, nframes );
+
+  // We have midi events in buffer
+  int evCount = jack_midi_get_event_count( buff );
+  for (int j = 0; j < evCount; j++) {
+    MidiInApi::MidiMessage message;
+    message.bytes.clear();
+
+    jack_midi_event_get( &event, buff, j );
+
+    for ( unsigned int i = 0; i < event.size; i++ )
+      message.bytes.push_back( event.buffer[i] );
+
+    // Compute the delta time.
+    time = jack_get_time();
+    if ( rtData->firstMessage == true )
+      rtData->firstMessage = false;
+    else
+      message.timeStamp = ( time - jData->lastTime ) * 0.000001;
+
+    jData->lastTime = time;
+
+    if ( !rtData->continueSysex ) {
+      if ( rtData->usingCallback ) {
+        RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;
+        callback( message.timeStamp, &message.bytes, rtData->userData );
+      }
+      else {
+        // As long as we haven't reached our queue size limit, push the message.
+        if ( rtData->queue.size < rtData->queue.ringSize ) {
+          rtData->queue.ring[rtData->queue.back++] = message;
+          if ( rtData->queue.back == rtData->queue.ringSize )
+            rtData->queue.back = 0;
+          rtData->queue.size++;
+        }
+        else
+          std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";
+      }
+    }
+  }
+
+  return 0;
 }
 
-void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
 {
+  initialize( clientName );
 }
 
-void RtMidiIn :: openVirtualPort( std::string portName )
+void MidiInJack :: initialize( const std::string& clientName )
 {
+  JackMidiData *data = new JackMidiData;
+  apiData_ = (void *) data;
+
+  data->rtMidiIn = &inputData_;
+  data->port = NULL;
+  data->client = NULL;
+  this->clientName = clientName;
+
+  connect();
 }
 
-void RtMidiIn :: closePort( void )
+void MidiInJack :: connect()
 {
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  if ( data->client )
+    return;
+
+  // Initialize JACK client
+  if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
+    errorString_ = "MidiInJack::initialize: JACK server not running?";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  jack_set_process_callback( data->client, jackProcessIn, data );
+  jack_activate( data->client );
 }
 
-RtMidiIn :: ~RtMidiIn()
+MidiInJack :: ~MidiInJack()
 {
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  closePort();
+
+  if ( data->client )
+    jack_client_close( data->client );
+  delete data;
 }
 
-unsigned int RtMidiIn :: getPortCount()
+void MidiInJack :: openPort( unsigned int portNumber, const std::string portName )
 {
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+
+  // Creating new port
+  if ( data->port == NULL)
+    data->port = jack_port_register( data->client, portName.c_str(),
+                                     JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+  if ( data->port == NULL) {
+    errorString_ = "MidiInJack::openPort: JACK error creating port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Connecting to the output
+  std::string name = getPortName( portNumber );
+  jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );
+}
+
+void MidiInJack :: openVirtualPort( const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+                                     JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+  }
+}
+
+unsigned int MidiInJack :: getPortCount()
+{
+  int count = 0;
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  connect();
+  if ( !data->client )
     return 0;
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+  if ( ports == NULL ) return 0;
+  while ( ports[count] != NULL )
+    count++;
+
+  free( ports );
+
+  return count;
 }
 
-std::string RtMidiIn :: getPortName( unsigned int portNumber )
+std::string MidiInJack :: getPortName( unsigned int portNumber )
 {
-    return "";
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  std::string retStr("");
+
+  connect();
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+                                       JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+  // Check port validity
+  if ( ports == NULL ) {
+    errorString_ = "MidiInJack::getPortName: no ports available!";
+    error( RtMidiError::WARNING, errorString_ );
+    return retStr;
+  }
+
+  if ( ports[portNumber] == NULL ) {
+    std::ostringstream ost;
+    ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+  }
+  else retStr.assign( ports[portNumber] );
+
+  free( ports );
+  return retStr;
 }
 
-unsigned int RtMidiOut :: getPortCount()
+void MidiInJack :: closePort()
 {
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  if ( data->port == NULL ) return;
+  jack_port_unregister( data->client, data->port );
+  data->port = NULL;
+}
+
+//*********************************************************************//
+//  API: JACK
+//  Class Definitions: MidiOutJack
+//*********************************************************************//
+
+// Jack process callback
+static int jackProcessOut( jack_nframes_t nframes, void *arg )
+{
+  JackMidiData *data = (JackMidiData *) arg;
+  jack_midi_data_t *midiData;
+  int space;
+
+  // Is port created?
+  if ( data->port == NULL ) return 0;
+
+  void *buff = jack_port_get_buffer( data->port, nframes );
+  jack_midi_clear_buffer( buff );
+
+  while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {
+    jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) );
+    midiData = jack_midi_event_reserve( buff, 0, space );
+
+    jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );
+  }
+
+  return 0;
+}
+
+MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+void MidiOutJack :: initialize( const std::string& clientName )
+{
+  JackMidiData *data = new JackMidiData;
+  apiData_ = (void *) data;
+
+  data->port = NULL;
+  data->client = NULL;
+  this->clientName = clientName;
+
+  connect();
+}
+
+void MidiOutJack :: connect()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  if ( data->client )
+    return;
+  
+  // Initialize output ringbuffers  
+  data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+  data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+
+  // Initialize JACK client
+  if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
+    errorString_ = "MidiOutJack::initialize: JACK server not running?";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  jack_set_process_callback( data->client, jackProcessOut, data );
+  jack_activate( data->client );
+}
+
+MidiOutJack :: ~MidiOutJack()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  closePort();
+  
+  // Cleanup
+  jack_ringbuffer_free( data->buffSize );
+  jack_ringbuffer_free( data->buffMessage );
+  if ( data->client ) {
+    jack_client_close( data->client );
+  }
+
+  delete data;
+}
+
+void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+
+  // Creating new port
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+      JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiOutJack::openPort: JACK error creating port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Connecting to the output
+  std::string name = getPortName( portNumber );
+  jack_connect( data->client, jack_port_name( data->port ), name.c_str() );
+}
+
+void MidiOutJack :: openVirtualPort( const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+      JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+  }
+}
+
+unsigned int MidiOutJack :: getPortCount()
+{
+  int count = 0;
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  connect();
+  if ( !data->client )
     return 0;
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+    JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+  if ( ports == NULL ) return 0;
+  while ( ports[count] != NULL )
+    count++;
+
+  free( ports );
+
+  return count;
 }
 
-std::string RtMidiOut :: getPortName( unsigned int portNumber )
+std::string MidiOutJack :: getPortName( unsigned int portNumber )
 {
-    return "";
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  std::string retStr("");
+
+  connect();
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+    JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+  // Check port validity
+  if ( ports == NULL) {
+    errorString_ = "MidiOutJack::getPortName: no ports available!";
+    error( RtMidiError::WARNING, errorString_ );
+    return retStr;
+  }
+
+  if ( ports[portNumber] == NULL) {
+    std::ostringstream ost;
+    ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+  }
+  else retStr.assign( ports[portNumber] );
+
+  free( ports );
+  return retStr;
 }
 
-void RtMidiOut :: initialize( const std::string& /*clientName*/ )
+void MidiOutJack :: closePort()
 {
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  if ( data->port == NULL ) return;
+  jack_port_unregister( data->client, data->port );
+  data->port = NULL;
 }
 
-void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+void MidiOutJack :: sendMessage( std::vector<unsigned char> *message )
 {
+  int nBytes = message->size();
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  // Write full message to buffer
+  jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0],
+                         message->size() );
+  jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) );
 }
 
-void RtMidiOut :: closePort( void )
-{
-}
-
-void RtMidiOut :: openVirtualPort( std::string portName )
-{
-}
-
-RtMidiOut :: ~RtMidiOut()
-{
-}
-
-void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
-{
-}
-
-#endif /* __RTMIDI_DUMMY_ONLY__ */
-
+#endif  // __UNIX_JACK__
--- a/data/midi/rtmidi/RtMidi.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/midi/rtmidi/RtMidi.h	Mon Sep 17 13:51:14 2018 +0100
@@ -8,7 +8,7 @@
     RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
 
     RtMidi: realtime MIDI i/o C++ classes
-    Copyright (c) 2003-2009 Gary P. Scavone
+    Copyright (c) 2003-2016 Gary P. Scavone
 
     Permission is hereby granted, free of charge, to any person
     obtaining a copy of this software and associated documentation files
@@ -22,8 +22,9 @@
     included in all copies or substantial portions of the Software.
 
     Any person wishing to distribute modifications to the Software is
-    requested to send the modifications to the original developer so that
-    they can be incorporated into the canonical version.
+    asked to send the modifications to the original developer so that
+    they can be incorporated into the canonical version.  This is,
+    however, not a binding provision of this license.
 
     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
@@ -35,18 +36,108 @@
 */
 /**********************************************************************/
 
-// RtMidi: Version 1.0.8
+/*!
+  \file RtMidi.h
+ */
 
 #ifndef RTMIDI_H
 #define RTMIDI_H
 
-#include "RtError.h"
+#define RTMIDI_VERSION "2.1.1"
+
+#include <exception>
+#include <iostream>
 #include <string>
+#include <vector>
+
+/************************************************************************/
+/*! \class RtMidiError
+    \brief Exception handling class for RtMidi.
+
+    The RtMidiError class is quite simple but it does allow errors to be
+    "caught" by RtMidiError::Type. See the RtMidi documentation to know
+    which methods can throw an RtMidiError.
+*/
+/************************************************************************/
+
+class RtMidiError : public std::exception
+{
+ public:
+  //! Defined RtMidiError types.
+  enum Type {
+    WARNING,           /*!< A non-critical error. */
+    DEBUG_WARNING,     /*!< A non-critical error which might be useful for debugging. */
+    UNSPECIFIED,       /*!< The default, unspecified error type. */
+    NO_DEVICES_FOUND,  /*!< No devices found on system. */
+    INVALID_DEVICE,    /*!< An invalid device ID was specified. */
+    MEMORY_ERROR,      /*!< An error occured during memory allocation. */
+    INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
+    INVALID_USE,       /*!< The function was called incorrectly. */
+    DRIVER_ERROR,      /*!< A system driver error occured. */
+    SYSTEM_ERROR,      /*!< A system error occured. */
+    THREAD_ERROR       /*!< A thread error occured. */
+  };
+
+  //! The constructor.
+  RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {}
+ 
+  //! The destructor.
+  virtual ~RtMidiError( void ) throw() {}
+
+  //! Prints thrown error message to stderr.
+  virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; }
+
+  //! Returns the thrown error message type.
+  virtual const Type& getType(void) const throw() { return type_; }
+
+  //! Returns the thrown error message string.
+  virtual const std::string& getMessage(void) const throw() { return message_; }
+
+  //! Returns the thrown error message as a c-style string.
+  virtual const char* what( void ) const throw() { return message_.c_str(); }
+
+ protected:
+  std::string message_;
+  Type type_;
+};
+
+//! RtMidi error callback function prototype.
+/*!
+    \param type Type of error.
+    \param errorText Error description.
+
+    Note that class behaviour is undefined after a critical error (not
+    a warning) is reported.
+ */
+typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData );
+
+class MidiApi;
 
 class RtMidi
 {
  public:
 
+  //! MIDI API specifier arguments.
+  enum Api {
+    UNSPECIFIED,    /*!< Search for a working compiled API. */
+    MACOSX_CORE,    /*!< Macintosh OS-X Core Midi API. */
+    LINUX_ALSA,     /*!< The Advanced Linux Sound Architecture API. */
+    UNIX_JACK,      /*!< The JACK Low-Latency MIDI Server API. */
+    WINDOWS_MM,     /*!< The Microsoft Multimedia MIDI API. */
+    RTMIDI_DUMMY    /*!< A compilable but non-functional API. */
+  };
+
+  //! A static function to determine the current RtMidi version.
+  static std::string getVersion( void ) throw();
+
+  //! A static function to determine the available compiled MIDI APIs.
+  /*!
+    The values returned in the std::vector can be compared against
+    the enumerated list values.  Note that there can be more than one
+    API compiled for certain operating systems.
+  */
+  static void getCompiledApi( std::vector<RtMidi::Api> &apis ) throw();
+
   //! Pure virtual openPort() function.
   virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0;
 
@@ -62,19 +153,22 @@
   //! Pure virtual closePort() function.
   virtual void closePort( void ) = 0;
 
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen( void ) const = 0;
+
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0;
+
  protected:
 
   RtMidi();
-  virtual ~RtMidi() {};
+  virtual ~RtMidi();
 
-  // A basic error reporting function for internal use in the RtMidi
-  // subclasses.  The behavior of this function can be modified to
-  // suit specific needs.
-  void error( RtError::Type type );
-
-  void *apiData_;
-  bool connected_;
-  std::string errorString_;
+  MidiApi *rtapi_;
 };
 
 /**********************************************************************/
@@ -87,16 +181,27 @@
     retrieval using the getMessage() function or immediately passed to
     a user-specified callback function.  Create multiple instances of
     this class to connect to more than one MIDI device at the same
-    time.  With the OS-X and Linux ALSA MIDI APIs, it is also possible
-    to open a virtual input port to which other MIDI software clients
-    can connect.
+    time.  With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also
+    possible to open a virtual input port to which other MIDI software
+    clients can connect.
 
-    by Gary P. Scavone, 2003-2008.
+    by Gary P. Scavone, 2003-2014.
 */
 /**********************************************************************/
 
-#include <vector>
-#include <queue>
+// **************************************************************** //
+//
+// RtMidiIn and RtMidiOut class declarations.
+//
+// RtMidiIn / RtMidiOut are "controllers" used to select an available
+// MIDI input or output interface.  They present common APIs for the
+// user to call but all functionality is implemented by the classes
+// MidiInApi, MidiOutApi and their subclasses.  RtMidiIn and RtMidiOut
+// each create an instance of a MidiInApi or MidiOutApi subclass based
+// on the user's API choice.  If no choice is made, they attempt to
+// make a "logical" API selection.
+//
+// **************************************************************** //
 
 class RtMidiIn : public RtMidi
 {
@@ -105,123 +210,122 @@
   //! User callback function type definition.
   typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData);
 
-  //! Default constructor that allows an optional client name.
+  //! Default constructor that allows an optional api, client name and queue size.
   /*!
-      An exception will be thrown if a MIDI system initialization error occurs.
+    An exception will be thrown if a MIDI system initialization
+    error occurs.  The queue size defines the maximum number of
+    messages that can be held in the MIDI queue (when not using a
+    callback function).  If the queue size limit is reached,
+    incoming messages will be ignored.
+
+    If no API argument is specified and multiple API support has been
+    compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+    JACK (OS-X).
+
+    \param api        An optional API id can be specified.
+    \param clientName An optional client name can be specified. This
+                      will be used to group the ports that are created
+                      by the application.
+    \param queueSizeLimit An optional size of the MIDI input queue can be specified.
   */
-  RtMidiIn( const std::string clientName = std::string( "RtMidi Input Client") );
+  RtMidiIn( RtMidi::Api api=UNSPECIFIED,
+            const std::string clientName = std::string( "RtMidi Input Client"),
+            unsigned int queueSizeLimit = 100 );
 
   //! If a MIDI connection is still open, it will be closed by the destructor.
-  ~RtMidiIn();
+  ~RtMidiIn ( void ) throw();
 
-  //! Open a MIDI input connection.
+  //! Returns the MIDI API specifier for the current instance of RtMidiIn.
+  RtMidi::Api getCurrentApi( void ) throw();
+
+  //! Open a MIDI input connection given by enumeration number.
   /*!
-      An optional port number greater than 0 can be specified.
-      Otherwise, the default or first port found is opened.
+    \param portNumber An optional port number greater than 0 can be specified.
+                      Otherwise, the default or first port found is opened.
+    \param portName An optional name for the application port that is used to connect to portId can be specified.
   */
-  void openPort( unsigned int portNumber = 0, const std::string Portname = std::string( "RtMidi Input" ) );
+  void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) );
 
-  //! Create a virtual input port, with optional name, to allow software connections (OS X and ALSA only).
+  //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only).
   /*!
-      This function creates a virtual MIDI input port to which other
-      software applications can connect.  This type of functionality
-      is currently only supported by the Macintosh OS-X and Linux ALSA
-      APIs (the function does nothing for the other APIs).
+    This function creates a virtual MIDI input port to which other
+    software applications can connect.  This type of functionality
+    is currently only supported by the Macintosh OS-X, any JACK,
+    and Linux ALSA APIs (the function returns an error for the other APIs).
+
+    \param portName An optional name for the application port that is
+                    used to connect to portId can be specified.
   */
   void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) );
 
   //! Set a callback function to be invoked for incoming MIDI messages.
   /*!
-      The callback function will be called whenever an incoming MIDI
-      message is received.  While not absolutely necessary, it is best
-      to set the callback function before opening a MIDI port to avoid
-      leaving some messages in the queue.
+    The callback function will be called whenever an incoming MIDI
+    message is received.  While not absolutely necessary, it is best
+    to set the callback function before opening a MIDI port to avoid
+    leaving some messages in the queue.
+
+    \param callback A callback function must be given.
+    \param userData Optionally, a pointer to additional data can be
+                    passed to the callback function whenever it is called.
   */
   void setCallback( RtMidiCallback callback, void *userData = 0 );
 
   //! Cancel use of the current callback function (if one exists).
   /*!
-      Subsequent incoming MIDI messages will be written to the queue
-      and can be retrieved with the \e getMessage function.
+    Subsequent incoming MIDI messages will be written to the queue
+    and can be retrieved with the \e getMessage function.
   */
   void cancelCallback();
 
   //! Close an open MIDI connection (if one exists).
   void closePort( void );
 
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen() const;
+
   //! Return the number of available MIDI input ports.
+  /*!
+    \return This function returns the number of MIDI ports of the selected API.
+  */
   unsigned int getPortCount();
 
   //! Return a string identifier for the specified MIDI input port number.
   /*!
-      An exception is thrown if an invalid port specifier is provided.
+    \return The name of the port with the given Id is returned.
+    \retval An empty string is returned if an invalid port specifier is provided.
   */
   std::string getPortName( unsigned int portNumber = 0 );
 
-  //! Set the maximum number of MIDI messages to be saved in the queue.
-  /*!
-      If the queue size limit is reached, incoming messages will be
-      ignored.  The default limit is 1024.
-  */
-  void setQueueSizeLimit( unsigned int queueSize );
-
   //! Specify whether certain MIDI message types should be queued or ignored during input.
   /*!
-      By default, MIDI timing and active sensing messages are ignored
-      during message input because of their relative high data rates.
-      MIDI sysex messages are ignored by default as well.  Variable
-      values of "true" imply that the respective message type will be
-      ignored.
+    By default, MIDI timing and active sensing messages are ignored
+    during message input because of their relative high data rates.
+    MIDI sysex messages are ignored by default as well.  Variable
+    values of "true" imply that the respective message type will be
+    ignored.
   */
   void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true );
 
   //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds.
   /*!
-      This function returns immediately whether a new message is
-      available or not.  A valid message is indicated by a non-zero
-      vector size.  An exception is thrown if an error occurs during
-      message retrieval or an input connection was not previously
-      established.
+    This function returns immediately whether a new message is
+    available or not.  A valid message is indicated by a non-zero
+    vector size.  An exception is thrown if an error occurs during
+    message retrieval or an input connection was not previously
+    established.
   */
   double getMessage( std::vector<unsigned char> *message );
 
-  // A MIDI structure used internally by the class to store incoming
-  // messages.  Each message represents one and only one MIDI message.
-  struct MidiMessage { 
-    std::vector<unsigned char> bytes; 
-    double timeStamp;
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
 
-    // Default constructor.
-    MidiMessage()
-      :bytes(3), timeStamp(0.0) {}
-  };
-
-  // The RtMidiInData structure is used to pass private class data to
-  // the MIDI input handling function or thread.
-  struct RtMidiInData {
-    std::queue<MidiMessage> queue;
-    MidiMessage message;
-    unsigned int queueLimit;
-    unsigned char ignoreFlags;
-    bool doInput;
-    bool firstMessage;
-    void *apiData;
-    bool usingCallback;
-    void *userCallback;
-    void *userData;
-    bool continueSysex;
-
-    // Default constructor.
-    RtMidiInData()
-      : queueLimit(1024), ignoreFlags(7), doInput(false), firstMessage(true),
-        apiData(0), usingCallback(false), userCallback(0), userData(0),
-        continueSysex(false) {}
-  };
-
- private:
-
-  void initialize( const std::string& clientName );
-  RtMidiInData inputData_;
+ protected:
+  void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit );
 
 };
 
@@ -233,9 +337,11 @@
     output.  It allows one to probe available MIDI output ports, to
     connect to one such port, and to send MIDI bytes immediately over
     the connection.  Create multiple instances of this class to
-    connect to more than one MIDI device at the same time.
+    connect to more than one MIDI device at the same time.  With the
+    OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a
+    virtual port to which other MIDI software clients can connect.
 
-    by Gary P. Scavone, 2003-2008.
+    by Gary P. Scavone, 2003-2014.
 */
 /**********************************************************************/
 
@@ -245,12 +351,20 @@
 
   //! Default constructor that allows an optional client name.
   /*!
-      An exception will be thrown if a MIDI system initialization error occurs.
+    An exception will be thrown if a MIDI system initialization error occurs.
+
+    If no API argument is specified and multiple API support has been
+    compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+    JACK (OS-X).
   */
-  RtMidiOut( const std::string clientName = std::string( "RtMidi Output Client" ) );
+  RtMidiOut( RtMidi::Api api=UNSPECIFIED,
+             const std::string clientName = std::string( "RtMidi Output Client") );
 
   //! The destructor closes any open MIDI connections.
-  ~RtMidiOut();
+  ~RtMidiOut( void ) throw();
+
+  //! Returns the MIDI API specifier for the current instance of RtMidiOut.
+  RtMidi::Api getCurrentApi( void ) throw();
 
   //! Open a MIDI output connection.
   /*!
@@ -262,25 +376,28 @@
   void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) );
 
   //! Close an open MIDI connection (if one exists).
-  void closePort();
+  void closePort( void );
 
-  //! Create a virtual output port, with optional name, to allow software connections (OS X and ALSA only).
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen() const;
+
+  //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only).
   /*!
       This function creates a virtual MIDI output port to which other
       software applications can connect.  This type of functionality
-      is currently only supported by the Macintosh OS-X and Linux ALSA
-      APIs (the function does nothing with the other APIs).  An
-      exception is thrown if an error occurs while attempting to create
-      the virtual port.
+      is currently only supported by the Macintosh OS-X, Linux ALSA
+      and JACK APIs (the function does nothing with the other APIs).
+      An exception is thrown if an error occurs while attempting to
+      create the virtual port.
   */
   void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) );
 
   //! Return the number of available MIDI output ports.
-  unsigned int getPortCount();
+  unsigned int getPortCount( void );
 
   //! Return a string identifier for the specified MIDI port type and number.
   /*!
-      An exception is thrown if an invalid port specifier is provided.
+      An empty string is returned if an invalid port specifier is provided.
   */
   std::string getPortName( unsigned int portNumber = 0 );
 
@@ -291,9 +408,356 @@
   */
   void sendMessage( std::vector<unsigned char> *message );
 
- private:
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
 
+ protected:
+  void openMidiApi( RtMidi::Api api, const std::string clientName );
+};
+
+
+// **************************************************************** //
+//
+// MidiInApi / MidiOutApi class declarations.
+//
+// Subclasses of MidiInApi and MidiOutApi contain all API- and
+// OS-specific code necessary to fully implement the RtMidi API.
+//
+// Note that MidiInApi and MidiOutApi are abstract base classes and
+// cannot be explicitly instantiated.  RtMidiIn and RtMidiOut will
+// create instances of a MidiInApi or MidiOutApi subclass.
+//
+// **************************************************************** //
+
+class MidiApi
+{
+ public:
+
+  MidiApi();
+  virtual ~MidiApi();
+  virtual RtMidi::Api getCurrentApi( void ) = 0;
+  virtual void openPort( unsigned int portNumber, const std::string portName ) = 0;
+  virtual void openVirtualPort( const std::string portName ) = 0;
+  virtual void closePort( void ) = 0;
+
+  virtual unsigned int getPortCount( void ) = 0;
+  virtual std::string getPortName( unsigned int portNumber ) = 0;
+
+  inline bool isPortOpen() const { return connected_; }
+  void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData );
+
+  //! A basic error reporting function for RtMidi classes.
+  void error( RtMidiError::Type type, std::string errorString );
+
+protected:
+  virtual void initialize( const std::string& clientName ) = 0;
+
+  void *apiData_;
+  bool connected_;
+  std::string errorString_;
+  RtMidiErrorCallback errorCallback_;
+  bool firstErrorOccurred_;
+  void *errorCallbackUserData_;
+};
+
+class MidiInApi : public MidiApi
+{
+ public:
+
+  MidiInApi( unsigned int queueSizeLimit );
+  virtual ~MidiInApi( void );
+  void setCallback( RtMidiIn::RtMidiCallback callback, void *userData );
+  void cancelCallback( void );
+  virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense );
+  double getMessage( std::vector<unsigned char> *message );
+
+  // A MIDI structure used internally by the class to store incoming
+  // messages.  Each message represents one and only one MIDI message.
+  struct MidiMessage { 
+    std::vector<unsigned char> bytes; 
+    double timeStamp;
+
+    // Default constructor.
+  MidiMessage()
+  :bytes(0), timeStamp(0.0) {}
+  };
+
+  struct MidiQueue {
+    unsigned int front;
+    unsigned int back;
+    unsigned int size;
+    unsigned int ringSize;
+    MidiMessage *ring;
+
+    // Default constructor.
+  MidiQueue()
+  :front(0), back(0), size(0), ringSize(0) {}
+  };
+
+  // The RtMidiInData structure is used to pass private class data to
+  // the MIDI input handling function or thread.
+  struct RtMidiInData {
+    MidiQueue queue;
+    MidiMessage message;
+    unsigned char ignoreFlags;
+    bool doInput;
+    bool firstMessage;
+    void *apiData;
+    bool usingCallback;
+    RtMidiIn::RtMidiCallback userCallback;
+    void *userData;
+    bool continueSysex;
+
+    // Default constructor.
+  RtMidiInData()
+  : ignoreFlags(7), doInput(false), firstMessage(true),
+      apiData(0), usingCallback(false), userCallback(0), userData(0),
+      continueSysex(false) {}
+  };
+
+ protected:
+  RtMidiInData inputData_;
+};
+
+class MidiOutApi : public MidiApi
+{
+ public:
+
+  MidiOutApi( void );
+  virtual ~MidiOutApi( void );
+  virtual void sendMessage( std::vector<unsigned char> *message ) = 0;
+};
+
+// **************************************************************** //
+//
+// Inline RtMidiIn and RtMidiOut definitions.
+//
+// **************************************************************** //
+
+inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); }
+inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); }
+inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }
+inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); }
+inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+
+inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiOut :: sendMessage( std::vector<unsigned char> *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); }
+inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+
+// **************************************************************** //
+//
+// MidiInApi and MidiOutApi subclass prototypes.
+//
+// **************************************************************** //
+
+#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)
+  #define __RTMIDI_DUMMY__
+#endif
+
+#if defined(__MACOSX_CORE__)
+
+class MidiInCore: public MidiInApi
+{
+ public:
+  MidiInCore( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInCore( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutCore: public MidiOutApi
+{
+ public:
+  MidiOutCore( const std::string clientName );
+  ~MidiOutCore( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
   void initialize( const std::string& clientName );
 };
 
 #endif
+
+#if defined(__UNIX_JACK__)
+
+class MidiInJack: public MidiInApi
+{
+ public:
+  MidiInJack( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInJack( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  std::string clientName;
+
+  void connect( void );
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutJack: public MidiOutApi
+{
+ public:
+  MidiOutJack( const std::string clientName );
+  ~MidiOutJack( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  std::string clientName;
+
+  void connect( void );
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__LINUX_ALSA__)
+
+class MidiInAlsa: public MidiInApi
+{
+ public:
+  MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInAlsa( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutAlsa: public MidiOutApi
+{
+ public:
+  MidiOutAlsa( const std::string clientName );
+  ~MidiOutAlsa( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__WINDOWS_MM__)
+
+class MidiInWinMM: public MidiInApi
+{
+ public:
+  MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInWinMM( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutWinMM: public MidiOutApi
+{
+ public:
+  MidiOutWinMM( const std::string clientName );
+  ~MidiOutWinMM( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__RTMIDI_DUMMY__)
+
+class MidiInDummy: public MidiInApi
+{
+ public:
+ MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+  void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
+  void openVirtualPort( const std::string /*portName*/ ) {}
+  void closePort( void ) {}
+  unsigned int getPortCount( void ) { return 0; }
+  std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
+
+ protected:
+  void initialize( const std::string& /*clientName*/ ) {}
+};
+
+class MidiOutDummy: public MidiOutApi
+{
+ public:
+  MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+  void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
+  void openVirtualPort( const std::string /*portName*/ ) {}
+  void closePort( void ) {}
+  unsigned int getPortCount( void ) { return 0; }
+  std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
+  void sendMessage( std::vector<unsigned char> * /*message*/ ) {}
+
+ protected:
+  void initialize( const std::string& /*clientName*/ ) {}
+};
+
+#endif
+
+#endif
--- a/data/model/AggregateWaveModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/AggregateWaveModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -25,10 +25,15 @@
 AggregateWaveModel::m_zoomConstraint;
 
 AggregateWaveModel::AggregateWaveModel(ChannelSpecList channelSpecs) :
-    m_components(channelSpecs)
+    m_components(channelSpecs),
+    m_invalidated(false)
 {
     for (ChannelSpecList::const_iterator i = channelSpecs.begin();
          i != channelSpecs.end(); ++i) {
+
+        connect(i->model, SIGNAL(aboutToBeDeleted()),
+                this, SLOT(componentModelAboutToBeDeleted()));
+        
         if (i->model->getSampleRate() !=
             channelSpecs.begin()->model->getSampleRate()) {
             SVDEBUG << "AggregateWaveModel::AggregateWaveModel: WARNING: Component models do not all have the same sample rate" << endl;
@@ -41,12 +46,27 @@
 {
 }
 
+void
+AggregateWaveModel::componentModelAboutToBeDeleted()
+{
+    SVDEBUG << "AggregateWaveModel::componentModelAboutToBeDeleted: invalidating"
+            << endl;
+    m_components.clear();
+    m_invalidated = true;
+    emit modelInvalidated();
+}
+
 bool
 AggregateWaveModel::isOK() const
 {
+    if (m_invalidated) {
+        return false;
+    }
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
-        if (!i->model->isOK()) return false;
+        if (!i->model->isOK()) {
+            return false;
+        }
     }
     return true;
 }
@@ -55,6 +75,7 @@
 AggregateWaveModel::isReady(int *completion) const
 {
     if (completion) *completion = 100;
+
     bool ready = true;
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
@@ -71,13 +92,12 @@
 AggregateWaveModel::getFrameCount() const
 {
     sv_frame_t count = 0;
-
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
-        sv_frame_t thisCount = i->model->getEndFrame() - i->model->getStartFrame();
+        sv_frame_t thisCount =
+            i->model->getEndFrame() - i->model->getStartFrame();
         if (thisCount > count) count = thisCount;
     }
-
     return count;
 }
 
@@ -94,7 +114,7 @@
     return m_components.begin()->model->getSampleRate();
 }
 
-vector<float>
+floatvec_t
 AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
 {
     int ch0 = channel, ch1 = channel;
@@ -103,8 +123,7 @@
         ch1 = getChannelCount()-1;
     }
 
-    vector<float> result(count, 0.f);
-
+    floatvec_t result(count, 0.f);
     sv_frame_t longest = 0;
     
     for (int c = ch0; c <= ch1; ++c) {
@@ -123,13 +142,13 @@
     return result;
 }
 
-vector<vector<float>>
+vector<floatvec_t>
 AggregateWaveModel::getMultiChannelData(int fromchannel, int tochannel,
                                         sv_frame_t start, sv_frame_t count) const
 {
     sv_frame_t min = count;
 
-    vector<vector<float>> result;
+    vector<floatvec_t> result;
 
     for (int c = fromchannel; c <= tochannel; ++c) {
         auto here = getData(c, start, count);
@@ -198,10 +217,17 @@
 }
 
 void
-AggregateWaveModel::toXml(QTextStream &,
-                          QString ,
-                          QString ) const
+AggregateWaveModel::toXml(QTextStream &out,
+                          QString indent,
+                          QString extraAttributes) const
 {
-    //!!! complete
+    QStringList componentStrings;
+    for (const auto &c: m_components) {
+        componentStrings.push_back(QString("%1").arg(getObjectExportId(c.model)));
+    }
+    Model::toXml(out, indent,
+                 QString("type=\"aggregatewave\" components=\"%1\" %2")
+                 .arg(componentStrings.join(","))
+                 .arg(extraAttributes));
 }
 
--- a/data/model/AggregateWaveModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/AggregateWaveModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -59,9 +59,9 @@
     virtual sv_frame_t getStartFrame() const { return 0; }
     virtual sv_frame_t getEndFrame() const { return getFrameCount(); }
 
-    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
+    virtual floatvec_t 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 std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
 
     virtual int getSummaryBlockSize(int desired) const;
 
@@ -79,15 +79,18 @@
     void modelChanged();
     void modelChangedWithin(sv_frame_t, sv_frame_t);
     void completionChanged();
+    void modelInvalidated();
 
 protected slots:
     void componentModelChanged();
     void componentModelChangedWithin(sv_frame_t, sv_frame_t);
     void componentModelCompletionChanged();
+    void componentModelAboutToBeDeleted();
 
 protected:
     ChannelSpecList m_components;
     static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
+    bool m_invalidated; // because one of its component models is aboutToBeDeleted
 };
 
 #endif
--- a/data/model/AlignmentModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/AlignmentModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -22,7 +22,7 @@
 AlignmentModel::AlignmentModel(Model *reference,
                                Model *aligned,
                                Model *inputModel,
-			       SparseTimeValueModel *path) :
+                               SparseTimeValueModel *path) :
     m_reference(reference),
     m_aligned(aligned),
     m_inputModel(inputModel),
--- a/data/model/Dense3DModelPeakCache.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/Dense3DModelPeakCache.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -17,8 +17,10 @@
 
 #include "base/Profiler.h"
 
+#include "base/HitCount.h"
+
 Dense3DModelPeakCache::Dense3DModelPeakCache(const DenseThreeDimensionalModel *source,
-					     int columnsPerPeak) :
+                                             int columnsPerPeak) :
     m_source(source),
     m_columnsPerPeak(columnsPerPeak)
 {
@@ -33,18 +35,17 @@
             this, SLOT(sourceModelChanged()));
     connect(source, SIGNAL(aboutToBeDeleted()),
             this, SLOT(sourceModelAboutToBeDeleted()));
-
 }
 
 Dense3DModelPeakCache::~Dense3DModelPeakCache()
 {
+    if (m_cache) m_cache->aboutToDelete();
     delete m_cache;
 }
 
 Dense3DModelPeakCache::Column
 Dense3DModelPeakCache::getColumn(int column) const
 {
-    Profiler profiler("Dense3DModelPeakCache::getColumn");
     if (!m_source) return Column();
     if (!haveColumn(column)) fillColumn(column);
     return m_cache->getColumn(column);
@@ -79,7 +80,14 @@
 bool
 Dense3DModelPeakCache::haveColumn(int column) const
 {
-    return in_range_for(m_coverage, column) && m_coverage[column];
+    static HitCount count("Dense3DModelPeakCache");
+    if (in_range_for(m_coverage, column) && m_coverage[column]) {
+        count.hit();
+        return true;
+    } else {
+        count.miss();
+        return false;
+    }
 }
 
 void
--- a/data/model/Dense3DModelPeakCache.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/Dense3DModelPeakCache.h	Mon Sep 17 13:51:14 2018 +0100
@@ -73,9 +73,15 @@
         return m_source->getMaximumLevel();
     }
 
-    virtual Column getColumn(int column) const;
+    /**
+     * Retrieve the peaks column at peak-cache column number col. This
+     * will consist of the peak values in the underlying model from
+     * columns (col * getColumnsPerPeak()) to ((col+1) *
+     * getColumnsPerPeak() - 1) inclusive.
+     */
+    virtual Column getColumn(int col) const;
 
-    virtual float getValueAt(int column, int n) const;
+    virtual float getValueAt(int col, int n) const;
 
     virtual QString getBinName(int n) const {
         return m_source->getBinName(n);
--- a/data/model/DenseTimeValueModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/DenseTimeValueModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -27,13 +27,13 @@
 {
     PlayParameterRepository::getInstance()->removePlayable(this);
 }
-	
+        
 QString
 DenseTimeValueModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
 {
     int ch = getChannelCount();
 
-    cerr << "f0 = " << f0 << ", f1 = " << f1 << endl;
+//    cerr << "f0 = " << f0 << ", f1 = " << f1 << endl;
 
     if (f1 <= f0) return "";
 
--- a/data/model/DenseTimeValueModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/DenseTimeValueModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _DENSE_TIME_VALUE_MODEL_H_
-#define _DENSE_TIME_VALUE_MODEL_H_
+#ifndef SV_DENSE_TIME_VALUE_MODEL_H
+#define SV_DENSE_TIME_VALUE_MODEL_H
 
 #include <QObject>
 
@@ -64,7 +64,8 @@
      * If the channel is given as -1, mix all available channels and
      * return the result.
      */
-    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const = 0;
+    virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count)
+        const = 0;
 
     /**
      * Get the specified set of samples from given contiguous range of
@@ -72,12 +73,18 @@
      * format. Returned vector may have fewer samples than requested,
      * if the end of file was reached.
      */
-    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const = 0;
+    virtual std::vector<floatvec_t> 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, sv_frame_t f0, sv_frame_t f1) const;
+    virtual QString toDelimitedDataStringSubset(QString delimiter,
+                                                sv_frame_t f0, sv_frame_t f1)
+        const;
 
     QString getTypeName() const { return tr("Dense Time-Value"); }
 };
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -321,7 +321,7 @@
     QWriteLocker locker(&m_lock);
 
     while (index >= int(m_data.size())) {
-	m_data.push_back(Column());
+        m_data.push_back(Column());
         m_trunc.push_back(0);
     }
 
@@ -332,14 +332,14 @@
         if (ISNAN(value) || ISINF(value)) {
             continue;
         }
-	if (!m_haveExtents || value < m_minimum) {
-	    m_minimum = value;
-	    allChange = true;
-	}
-	if (!m_haveExtents || value > m_maximum) {
-	    m_maximum = value;
-	    allChange = true;
-	}
+        if (!m_haveExtents || value < m_minimum) {
+            m_minimum = value;
+            allChange = true;
+        }
+        if (!m_haveExtents || value > m_maximum) {
+            m_maximum = value;
+            allChange = true;
+        }
         m_haveExtents = true;
     }
 
@@ -351,26 +351,26 @@
     windowStart *= m_resolution;
 
     if (m_notifyOnAdd) {
-	if (allChange) {
-	    emit modelChanged();
-	} else {
-	    emit modelChangedWithin(windowStart, windowStart + m_resolution);
-	}
+        if (allChange) {
+            emit modelChanged();
+        } else {
+            emit modelChangedWithin(windowStart, windowStart + m_resolution);
+        }
     } else {
-	if (allChange) {
-	    m_sinceLastNotifyMin = -1;
-	    m_sinceLastNotifyMax = -1;
-	    emit modelChanged();
-	} else {
-	    if (m_sinceLastNotifyMin == -1 ||
-		windowStart < m_sinceLastNotifyMin) {
-		m_sinceLastNotifyMin = windowStart;
-	    }
-	    if (m_sinceLastNotifyMax == -1 ||
-		windowStart > m_sinceLastNotifyMax) {
-		m_sinceLastNotifyMax = windowStart;
-	    }
-	}
+        if (allChange) {
+            m_sinceLastNotifyMin = -1;
+            m_sinceLastNotifyMax = -1;
+            emit modelChanged();
+        } else {
+            if (m_sinceLastNotifyMin == -1 ||
+                windowStart < m_sinceLastNotifyMin) {
+                m_sinceLastNotifyMin = windowStart;
+            }
+            if (m_sinceLastNotifyMax == -1 ||
+                windowStart > m_sinceLastNotifyMax) {
+                m_sinceLastNotifyMax = windowStart;
+            }
+        }
     }
 }
 
@@ -455,34 +455,34 @@
         if (n[j]) sample[j] /= n[j];
     }
     
-    return LogRange::useLogScale(sample);
+    return LogRange::shouldUseLogScale(sample);
 }
 
 void
 EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update)
 {
     if (m_completion != completion) {
-	m_completion = completion;
+        m_completion = completion;
 
-	if (completion == 100) {
+        if (completion == 100) {
 
-	    m_notifyOnAdd = true; // henceforth
-	    emit modelChanged();
+            m_notifyOnAdd = true; // henceforth
+            emit modelChanged();
 
-	} else if (!m_notifyOnAdd) {
+        } else if (!m_notifyOnAdd) {
 
-	    if (update &&
+            if (update &&
                 m_sinceLastNotifyMin >= 0 &&
-		m_sinceLastNotifyMax >= 0) {
-		emit modelChangedWithin(m_sinceLastNotifyMin,
+                m_sinceLastNotifyMax >= 0) {
+                emit modelChangedWithin(m_sinceLastNotifyMin,
                                         m_sinceLastNotifyMax + m_resolution);
-		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
-	    } else {
-		emit completionChanged();
-	    }
-	} else {
-	    emit completionChanged();
-	}	    
+                m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
+            } else {
+                emit completionChanged();
+            }
+        } else {
+            emit completionChanged();
+        }            
     }
 }
 
@@ -493,7 +493,7 @@
     QString s;
     for (int i = 0; in_range_for(m_data, i); ++i) {
         QStringList list;
-	for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
+        for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
             list << QString("%1").arg(m_data.at(i).at(j));
         }
         s += list.join(delimiter) + "\n";
@@ -531,36 +531,36 @@
     SVDEBUG << "EditableDenseThreeDimensionalModel::toXml" << endl;
 
     Model::toXml
-	(out, indent,
+        (out, indent,
          QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7")
-	 .arg(m_resolution)
-	 .arg(m_yBinCount)
-	 .arg(m_minimum)
-	 .arg(m_maximum)
-	 .arg(getObjectExportId(&m_data))
+         .arg(m_resolution)
+         .arg(m_yBinCount)
+         .arg(m_minimum)
+         .arg(m_maximum)
+         .arg(getObjectExportId(&m_data))
          .arg(m_startFrame)
-	 .arg(extraAttributes));
+         .arg(extraAttributes));
 
     out << indent;
     out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
-	.arg(getObjectExportId(&m_data));
+        .arg(getObjectExportId(&m_data));
 
     for (int i = 0; i < (int)m_binNames.size(); ++i) {
-	if (m_binNames[i] != "") {
-	    out << indent + "  ";
-	    out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
-		.arg(i).arg(m_binNames[i]);
-	}
+        if (m_binNames[i] != "") {
+            out << indent + "  ";
+            out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
+                .arg(i).arg(m_binNames[i]);
+        }
     }
 
     for (int i = 0; i < (int)m_data.size(); ++i) {
-	out << indent + "  ";
-	out << QString("<row n=\"%1\">").arg(i);
-	for (int j = 0; j < (int)m_data.at(i).size(); ++j) {
-	    if (j > 0) out << " ";
-	    out << m_data.at(i).at(j);
-	}
-	out << QString("</row>\n");
+        out << indent + "  ";
+        out << QString("<row n=\"%1\">").arg(i);
+        for (int j = 0; j < (int)m_data.at(i).size(); ++j) {
+            if (j > 0) out << " ";
+            out << m_data.at(i).at(j);
+        }
+        out << QString("</row>\n");
         out.flush();
     }
 
--- a/data/model/EditableDenseThreeDimensionalModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -43,10 +43,10 @@
     };
 
     EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
-				       int resolution,
-				       int height,
+                                       int resolution,
+                                       int height,
                                        CompressionType compression,
-				       bool notifyOnAdd = true);
+                                       bool notifyOnAdd = true);
 
     virtual bool isOK() const;
 
--- a/data/model/FFTModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/FFTModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -19,6 +19,7 @@
 #include "base/Profiler.h"
 #include "base/Pitch.h"
 #include "base/HitCount.h"
+#include "base/Debug.h"
 
 #include <algorithm>
 
@@ -44,11 +45,16 @@
     m_fftSize(fftSize),
     m_windower(windowType, windowSize),
     m_fft(fftSize),
+    m_cacheWriteIndex(0),
     m_cacheSize(3)
 {
+    while (m_cached.size() < m_cacheSize) {
+        m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) });
+    }
+    
     if (m_windowSize > m_fftSize) {
-        cerr << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize
-             << ") must be at least FFT size (" << m_fftSize << ")" << endl;
+        SVCERR << "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");
     }
 
@@ -67,7 +73,7 @@
 FFTModel::sourceModelAboutToBeDeleted()
 {
     if (m_model) {
-        cerr << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl;
+        SVDEBUG << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl;
         m_model = 0;
     }
 }
@@ -188,7 +194,7 @@
     return true;
 }
 
-vector<float>
+FFTModel::fvec
 FFTModel::getSourceSamples(int column) const
 {
     // m_fftSize may be greater than m_windowSize, but not the reverse
@@ -204,7 +210,7 @@
         return data;
     } else {
         vector<float> pad(off, 0.f);
-        vector<float> padded;
+        fvec padded;
         padded.reserve(m_fftSize);
         padded.insert(padded.end(), pad.begin(), pad.end());
         padded.insert(padded.end(), data.begin(), data.end());
@@ -213,7 +219,7 @@
     }
 }
 
-vector<float>
+FFTModel::fvec
 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const
 {
 //    cerr << "getSourceData(" << range.first << "," << range.second
@@ -235,16 +241,20 @@
         
         sv_frame_t discard = range.first - m_savedData.range.first;
 
-        vector<float> acc(m_savedData.data.begin() + discard,
-                          m_savedData.data.end());
+        fvec data;
+        data.reserve(range.second - range.first);
 
-        vector<float> rest =
-            getSourceDataUncached({ m_savedData.range.second, range.second });
+        data.insert(data.end(),
+                    m_savedData.data.begin() + discard,
+                    m_savedData.data.end());
 
-        acc.insert(acc.end(), rest.begin(), rest.end());
+        fvec rest = getSourceDataUncached
+            ({ m_savedData.range.second, range.second });
+
+        data.insert(data.end(), rest.begin(), rest.end());
         
-        m_savedData = { range, acc };
-        return acc;
+        m_savedData = { range, data };
+        return data;
 
     } else {
 
@@ -256,9 +266,11 @@
     }
 }
 
-vector<float>
+FFTModel::fvec
 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const
 {
+    Profiler profiler("FFTModel::getSourceDataUncached");
+    
     decltype(range.first) pfx = 0;
     if (range.first < 0) {
         pfx = -range.first;
@@ -284,21 +296,21 @@
     }
     
     if (m_channel == -1) {
-	int channels = m_model->getChannelCount();
-	if (channels > 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;
-	    }
-	}
+            for (int i = 0; i < n; ++i) {
+                data[i] *= factor;
+            }
+        }
     }
     
     return data;
 }
 
-vector<complex<float>>
+const FFTModel::cvec &
 FFTModel::getFFTColumn(int n) const
 {
     // The small cache (i.e. the m_cached deque) is for cases where
@@ -321,16 +333,14 @@
     m_windower.cut(samples.data());
     breakfastquay::v_fftshift(samples.data(), m_fftSize);
 
-    vector<complex<float>> col(m_fftSize/2 + 1);
+    cvec &col = m_cached[m_cacheWriteIndex].col;
     
     m_fft.forwardInterleaved(samples.data(),
                              reinterpret_cast<float *>(col.data()));
 
-    SavedColumn sc { n, col };
-    if (m_cached.size() >= m_cacheSize) {
-        m_cached.pop_front();
-    }
-    m_cached.push_back(sc);
+    m_cached[m_cacheWriteIndex].n = n;
+
+    m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize;
 
     return col;
 }
--- a/data/model/FFTModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/FFTModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -22,11 +22,11 @@
 #include "base/Window.h"
 
 #include <bqfft/FFT.h>
+#include <bqvec/Allocators.h>
 
 #include <set>
 #include <vector>
 #include <complex>
-#include <deque>
 
 /**
  * An implementation of DenseThreeDimensionalModel that makes FFT data
@@ -167,22 +167,27 @@
         return { startFrame, endFrame };
     }
 
-    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;
+    typedef std::vector<float, breakfastquay::StlAllocator<float>> fvec;
+    typedef std::vector<std::complex<float>,
+                        breakfastquay::StlAllocator<std::complex<float>>> cvec;
+    
+    const cvec &getFFTColumn(int column) const; // returns ref for immediate use only
+    fvec getSourceSamples(int column) const;
+    fvec getSourceData(std::pair<sv_frame_t, sv_frame_t>) const;
+    fvec getSourceDataUncached(std::pair<sv_frame_t, sv_frame_t>) const;
 
     struct SavedSourceData {
         std::pair<sv_frame_t, sv_frame_t> range;
-        std::vector<float> data;
+        fvec data;
     };
     mutable SavedSourceData m_savedData;
-    
+
     struct SavedColumn {
         int n;
-        std::vector<std::complex<float> > col;
+        cvec col;
     };
-    mutable std::deque<SavedColumn> m_cached;
+    mutable std::vector<SavedColumn> m_cached;
+    mutable size_t m_cacheWriteIndex;
     size_t m_cacheSize;
 };
 
--- a/data/model/FlexiNoteModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/FlexiNoteModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -44,7 +44,7 @@
 public:
     FlexiNote(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
     FlexiNote(sv_frame_t _frame, float _value, sv_frame_t _duration, float _level, QString _label) :
-	frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { }
+        frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { }
 
     int getDimensions() const { return 3; }
 
@@ -60,9 +60,9 @@
                QString indent = "",
                QString extraAttributes = "") const
     {
-	stream <<
+        stream <<
             QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" level=\"%5\" label=\"%6\" %7/>\n")
-	    .arg(indent).arg(frame).arg(value).arg(duration).arg(level)
+            .arg(indent).arg(frame).arg(value).arg(duration).arg(level)
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
@@ -80,21 +80,21 @@
     }
 
     struct Comparator {
-	bool operator()(const FlexiNote &p1,
-			const FlexiNote &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    if (p1.value != p2.value) return p1.value < p2.value;
-	    if (p1.duration != p2.duration) return p1.duration < p2.duration;
+        bool operator()(const FlexiNote &p1,
+                        const FlexiNote &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            if (p1.value != p2.value) return p1.value < p2.value;
+            if (p1.duration != p2.duration) return p1.duration < p2.duration;
             if (p1.level != p2.level) return p1.level < p2.level;
-	    return p1.label < p2.label;
-	}
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const FlexiNote &p1,
-			const FlexiNote &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const FlexiNote &p1,
+                        const FlexiNote &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -106,21 +106,21 @@
 public:
     FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
                    bool notifyOnAdd = true) :
-	IntervalModel<FlexiNote>(sampleRate, resolution, notifyOnAdd),
-	m_valueQuantization(0)
+        IntervalModel<FlexiNote>(sampleRate, resolution, notifyOnAdd),
+        m_valueQuantization(0)
     {
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
-	      float valueMinimum, float valueMaximum,
-	      bool notifyOnAdd = true) :
-	IntervalModel<FlexiNote>(sampleRate, resolution,
+              float valueMinimum, float valueMaximum,
+              bool notifyOnAdd = true) :
+        IntervalModel<FlexiNote>(sampleRate, resolution,
                             valueMinimum, valueMaximum,
                             notifyOnAdd),
-	m_valueQuantization(0)
+        m_valueQuantization(0)
     {
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     virtual ~FlexiNoteModel()
@@ -150,10 +150,10 @@
                   << extraAttributes.toStdString() << std::endl;
 
         IntervalModel<FlexiNote>::toXml
-	    (out,
+            (out,
              indent,
-	     QString("%1 subtype=\"flexinote\" valueQuantization=\"%2\"")
-	     .arg(extraAttributes).arg(m_valueQuantization));
+             QString("%1 subtype=\"flexinote\" valueQuantization=\"%2\"")
+             .arg(extraAttributes).arg(m_valueQuantization));
     }
 
     /**
@@ -235,10 +235,10 @@
 
     NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const 
     {    
-    	PointList points = getPoints(startFrame, endFrame);
+            PointList points = getPoints(startFrame, endFrame);
         NoteList notes;
         for (PointList::iterator pli = points.begin(); pli != points.end(); ++pli) {
-    	    sv_frame_t duration = pli->duration;
+                sv_frame_t duration = pli->duration;
             if (duration == 0 || duration == 1) {
                 duration = sv_frame_t(getSampleRate() / 20);
             }
--- a/data/model/ImageModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/ImageModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -47,9 +47,9 @@
                QString indent = "",
                QString extraAttributes = "") const
     {
-	stream <<
+        stream <<
             QString("%1<point frame=\"%2\" image=\"%3\" label=\"%4\" %5/>\n")
-	    .arg(indent).arg(frame)
+            .arg(indent).arg(frame)
             .arg(encodeEntities(image))
             .arg(encodeEntities(label))
             .arg(extraAttributes);
@@ -65,19 +65,19 @@
     }
 
     struct Comparator {
-	bool operator()(const ImagePoint &p1,
-			const ImagePoint &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+        bool operator()(const ImagePoint &p1,
+                        const ImagePoint &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
             if (p1.label != p2.label) return p1.label < p2.label;
-	    return p1.image < p2.image;
-	}
+            return p1.image < p2.image;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const ImagePoint &p1,
-			const ImagePoint &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const ImagePoint &p1,
+                        const ImagePoint &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -90,7 +90,7 @@
 
 public:
     ImageModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
-	SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd)
+        SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd)
     { }
 
     QString getTypeName() const { return tr("Image"); }
@@ -100,10 +100,10 @@
                        QString extraAttributes = "") const
     {
         SparseModel<ImagePoint>::toXml
-	    (out, 
+            (out, 
              indent,
-	     QString("%1 subtype=\"image\"")
-	     .arg(extraAttributes));
+             QString("%1 subtype=\"image\"")
+             .arg(extraAttributes));
     }
 
     /**
@@ -116,25 +116,25 @@
                            const ImagePoint &point,
                            QString newImage,
                            QString newLabel) :
-	    m_model(model), m_oldPoint(point), m_newPoint(point) {
-	    m_newPoint.image = newImage;
+            m_model(model), m_oldPoint(point), m_newPoint(point) {
+            m_newPoint.image = newImage;
             m_newPoint.label = newLabel;
-	}
+        }
 
-	virtual QString getName() const { return tr("Edit Image"); }
+        virtual QString getName() const { return tr("Edit Image"); }
 
-	virtual void execute() { 
-	    m_model->deletePoint(m_oldPoint);
-	    m_model->addPoint(m_newPoint);
-	    std::swap(m_oldPoint, m_newPoint);
-	}
+        virtual void execute() { 
+            m_model->deletePoint(m_oldPoint);
+            m_model->addPoint(m_newPoint);
+            std::swap(m_oldPoint, m_newPoint);
+        }
 
-	virtual void unexecute() { execute(); }
+        virtual void unexecute() { execute(); }
 
     private:
-	ImageModel *m_model;
-	ImagePoint m_oldPoint;
-	ImagePoint m_newPoint;
+        ImageModel *m_model;
+        ImagePoint m_oldPoint;
+        ImagePoint m_newPoint;
     };
 
     /**
--- a/data/model/IntervalModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/IntervalModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -31,13 +31,13 @@
 public:
     IntervalModel(sv_samplerate_t sampleRate, int resolution,
                   bool notifyOnAdd = true) :
-	SparseValueModel<PointType>(sampleRate, resolution, notifyOnAdd)
+        SparseValueModel<PointType>(sampleRate, resolution, notifyOnAdd)
     { }
 
     IntervalModel(sv_samplerate_t sampleRate, int resolution,
                   float valueMinimum, float valueMaximum,
                   bool notifyOnAdd = true) :
-	SparseValueModel<PointType>(sampleRate, resolution,
+        SparseValueModel<PointType>(sampleRate, resolution,
                                     valueMinimum, valueMaximum,
                                     notifyOnAdd)
     { }
--- a/data/model/Model.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/Model.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -27,9 +27,11 @@
 //    SVDEBUG << "Model::~Model(" << this << ")" << endl;
 
     if (!m_aboutToDelete) {
-        SVDEBUG << "NOTE: Model::~Model(" << this << ", \""
-                  << objectName() << "\"): Model deleted "
-                  << "with no aboutToDelete notification" << endl;
+        SVDEBUG << "NOTE: Model(" << this << ", \""
+                << objectName() << "\", type uri <"
+                << m_typeUri << ">)::~Model(): Model deleted "
+                << "with no aboutToDelete notification"
+                << endl;
     }
 
     if (m_alignment) {
@@ -38,6 +40,20 @@
     }
 }
 
+int
+Model::getNextId()
+{
+    static int nextId = 0;
+    static QMutex mutex;
+    QMutexLocker locker(&mutex);
+    int i = nextId;
+    if (nextId == INT_MAX) {
+        nextId = INT_MIN;
+    }
+    ++nextId;
+    return i;
+}
+
 void
 Model::setSourceModel(Model *model)
 {
@@ -59,13 +75,16 @@
 void
 Model::aboutToDelete()
 {
-//    cerr << "Model(" << this << ")::aboutToDelete()" << endl;
+//    SVDEBUG << "Model(" << this << ", \""
+//            << objectName() << "\", type uri <"
+//            << m_typeUri << ">)::aboutToDelete()" << endl;
 
     if (m_aboutToDelete) {
-        cerr << "WARNING: Model(" << this << ", \""
-                  << objectName() << "\")::aboutToDelete: "
-                  << "aboutToDelete called more than once for the same model"
-                  << endl;
+        SVDEBUG << "WARNING: Model(" << this << ", \""
+                << objectName() << "\", type uri <"
+                << m_typeUri << ">)::aboutToDelete: "
+                << "aboutToDelete called more than once for the same model"
+                << endl;
     }
 
     emit aboutToBeDeleted();
@@ -180,12 +199,12 @@
 {
     stream << indent;
     stream << QString("<model id=\"%1\" name=\"%2\" sampleRate=\"%3\" start=\"%4\" end=\"%5\" %6/>\n")
-	.arg(getObjectExportId(this))
-	.arg(encodeEntities(objectName()))
-	.arg(getSampleRate())
-	.arg(getStartFrame())
-	.arg(getEndFrame())
-	.arg(extraAttributes);
+        .arg(getObjectExportId(this))
+        .arg(encodeEntities(objectName()))
+        .arg(getSampleRate())
+        .arg(getStartFrame())
+        .arg(getEndFrame())
+        .arg(extraAttributes);
 }
 
 
--- a/data/model/Model.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/Model.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MODEL_H_
-#define _MODEL_H_
+#ifndef SV_MODEL_H
+#define SV_MODEL_H
 
 #include <vector>
 #include <QObject>
@@ -27,13 +27,15 @@
 class ZoomConstraint;
 class AlignmentModel;
 
+typedef int ModelId;
+
 /** 
  * Model is the base class for all data models that represent any sort
  * of data on a time scale based on an audio frame rate.
  */
 
 class Model : public QObject,
-	      public XmlExportable,
+              public XmlExportable,
               public Playable
 {
     Q_OBJECT
@@ -53,7 +55,11 @@
     virtual sv_frame_t getStartFrame() const = 0;
 
     /**
-     * Return the last audio frame spanned by the model.
+     * Return the audio frame at the end of the model, i.e. 1 more
+     * than the final frame contained within the model. The end frame
+     * minus the start frame should yield the total duration in frames
+     * spanned by the model. This is consistent with the definition of
+     * the end frame of a Selection object.
      */
     virtual sv_frame_t getEndFrame() const = 0;
 
@@ -91,6 +97,18 @@
     virtual QString getTypeName() const = 0;
 
     /**
+     * Return true if this is a sparse model.
+     */
+    virtual bool isSparse() const { return false; }
+
+    /**
+     * Return an id for this model. The id is guaranteed to be a
+     * unique identifier for this model among all models that may ever
+     * exist within this single run of the application.
+     */
+    ModelId getId() const { return m_id; }
+    
+    /**
      * 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
@@ -125,9 +143,9 @@
      * getCompletion().
      */
     virtual bool isReady(int *completion = 0) const {
-	bool ok = isOK();
-	if (completion) *completion = (ok ? 100 : 0);
-	return ok;
+        bool ok = isOK();
+        if (completion) *completion = (ok ? 100 : 0);
+        return ok;
     }
     static const int COMPLETION_UNKNOWN;
 
@@ -220,11 +238,11 @@
 
     virtual QString toDelimitedDataString(QString delimiter) const {
         return toDelimitedDataStringSubset
-            (delimiter, getStartFrame(), getEndFrame() + 1);
+            (delimiter, getStartFrame(), getEndFrame());
     }
     virtual QString toDelimitedDataStringWithOptions(QString delimiter, DataExportOptions opts) const {
         return toDelimitedDataStringSubsetWithOptions
-            (delimiter, opts, getStartFrame(), getEndFrame() + 1);
+            (delimiter, opts, getStartFrame(), getEndFrame());
     }
     virtual QString toDelimitedDataStringSubset(QString, sv_frame_t /* f0 */, sv_frame_t /* f1 */) const {
         return "";
@@ -282,7 +300,8 @@
     void aboutToBeDeleted();
 
 protected:
-    Model() : 
+    Model() :
+        m_id(getNextId()),
         m_sourceModel(0), 
         m_alignment(0), 
         m_abandoning(false), 
@@ -292,11 +311,14 @@
     Model(const Model &);
     Model &operator=(const Model &); 
 
+    const ModelId m_id;
     Model *m_sourceModel;
     AlignmentModel *m_alignment;
     QString m_typeUri;
     bool m_abandoning;
     bool m_aboutToDelete;
+
+    int getNextId();
 };
 
 #endif
--- a/data/model/ModelDataTableModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/ModelDataTableModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -73,16 +73,12 @@
     if (!m_model) return false;
     if (parent.isValid()) return false;
 
-    emit beginInsertRows(parent, row, row);
-
     Command *command = m_model->getInsertRowCommand(getUnsorted(row));
 
     if (command) {
         emit addCommand(command);
     }
 
-    emit endInsertRows();
-
     return (command ? true : false);
 }
 
@@ -92,16 +88,12 @@
     if (!m_model) return false;
     if (parent.isValid()) return false;
 
-    emit beginRemoveRows(parent, row, row);
-
     Command *command = m_model->getRemoveRowCommand(getUnsorted(row));
 
     if (command) {
         emit addCommand(command);
     }
 
-    emit endRemoveRows();
-
     return (command ? true : false);
 }
 
@@ -144,7 +136,8 @@
 {
     if (!m_model) return 0;
     if (parent.isValid()) return 0;
-    return m_model->getRowCount();
+    int count = m_model->getRowCount();
+    return count;
 }
 
 int
@@ -215,14 +208,37 @@
 void
 ModelDataTableModel::modelChanged()
 {
+    SVDEBUG << "ModelDataTableModel::modelChanged" << endl;
+    QModelIndex ix0;
+    QModelIndex ix1;
+    if (rowCount() > 0) {
+        ix0 = createIndex(0, 0);
+        int lastCol = columnCount() - 1;
+        if (lastCol < 0) lastCol = 0;
+        ix1 = createIndex(rowCount(), lastCol);
+    }
+    SVDEBUG << "emitting dataChanged from row " << ix0.row() << " to " << ix1.row() << endl;
+    emit dataChanged(ix0, ix1);
     clearSort();
     emit layoutChanged();
 }
 
 void 
-ModelDataTableModel::modelChangedWithin(sv_frame_t, sv_frame_t)
+ModelDataTableModel::modelChangedWithin(sv_frame_t f0, sv_frame_t f1)
 {
-    //!!! inefficient
+    SVDEBUG << "ModelDataTableModel::modelChangedWithin(" << f0 << "," << f1 << ")" << endl;
+    QModelIndex ix0 = getModelIndexForFrame(f0);
+    QModelIndex ix1 = getModelIndexForFrame(f1);
+    int row0 = ix0.row();
+    int row1 = ix1.row();
+    if (row0 > 0) {
+        ix0 = createIndex(row0 - 1, ix0.column(), (void *)0);
+    }
+    if (row1 + 1 < rowCount()) {
+        ix1 = createIndex(row1 + 1, ix1.column(), (void *)0);
+    }
+    SVDEBUG << "emitting dataChanged from row " << ix0.row() << " to " << ix1.row() << endl;
+    emit dataChanged(ix0, ix1);
     clearSort();
     emit layoutChanged();
 }
--- a/data/model/ModelDataTableModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/ModelDataTableModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MODEL_DATA_TABLE_MODEL_H_
-#define _MODEL_DATA_TABLE_MODEL_H_
+#ifndef SV_MODEL_DATA_TABLE_MODEL_H
+#define SV_MODEL_DATA_TABLE_MODEL_H
 
 #include <QAbstractItemModel>
 
--- a/data/model/NoteData.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/NoteData.h	Mon Sep 17 13:51:14 2018 +0100
@@ -12,8 +12,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef NOTE_DATA_H
-#define NOTE_DATA_H
+#ifndef SV_NOTE_DATA_H
+#define SV_NOTE_DATA_H
 
 #include <vector>
 
@@ -22,8 +22,8 @@
 struct NoteData
 {
     NoteData(sv_frame_t _start, sv_frame_t _dur, int _mp, int _vel) :
-	start(_start), duration(_dur), midiPitch(_mp), frequency(0),
-	isMidiPitchQuantized(true), velocity(_vel), channel(0) { };
+        start(_start), duration(_dur), midiPitch(_mp), frequency(0),
+        isMidiPitchQuantized(true), velocity(_vel), channel(0) { };
             
     sv_frame_t start;       // audio sample frame
     sv_frame_t duration;    // in audio sample frames
--- a/data/model/NoteModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/NoteModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _NOTE_MODEL_H_
-#define _NOTE_MODEL_H_
+#ifndef SV_NOTE_MODEL_H
+#define SV_NOTE_MODEL_H
 
 #include "IntervalModel.h"
 #include "NoteData.h"
@@ -40,7 +40,7 @@
 public:
     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) { }
+        frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { }
 
     int getDimensions() const { return 3; }
 
@@ -56,9 +56,9 @@
                QString indent = "",
                QString extraAttributes = "") const
     {
-	stream <<
+        stream <<
             QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" level=\"%5\" label=\"%6\" %7/>\n")
-	    .arg(indent).arg(frame).arg(value).arg(duration).arg(level)
+            .arg(indent).arg(frame).arg(value).arg(duration).arg(level)
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
@@ -75,21 +75,21 @@
     }
 
     struct Comparator {
-	bool operator()(const Note &p1,
-			const Note &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    if (p1.value != p2.value) return p1.value < p2.value;
-	    if (p1.duration != p2.duration) return p1.duration < p2.duration;
+        bool operator()(const Note &p1,
+                        const Note &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            if (p1.value != p2.value) return p1.value < p2.value;
+            if (p1.duration != p2.duration) return p1.duration < p2.duration;
             if (p1.level != p2.level) return p1.level < p2.level;
-	    return p1.label < p2.label;
-	}
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const Note &p1,
-			const Note &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const Note &p1,
+                        const Note &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -100,22 +100,22 @@
     
 public:
     NoteModel(sv_samplerate_t sampleRate, int resolution,
-	      bool notifyOnAdd = true) :
-	IntervalModel<Note>(sampleRate, resolution, notifyOnAdd),
-	m_valueQuantization(0)
+              bool notifyOnAdd = true) :
+        IntervalModel<Note>(sampleRate, resolution, notifyOnAdd),
+        m_valueQuantization(0)
     {
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     NoteModel(sv_samplerate_t sampleRate, int resolution,
-	      float valueMinimum, float valueMaximum,
-	      bool notifyOnAdd = true) :
-	IntervalModel<Note>(sampleRate, resolution,
+              float valueMinimum, float valueMaximum,
+              bool notifyOnAdd = true) :
+        IntervalModel<Note>(sampleRate, resolution,
                             valueMinimum, valueMaximum,
                             notifyOnAdd),
-	m_valueQuantization(0)
+        m_valueQuantization(0)
     {
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     virtual ~NoteModel()
@@ -143,10 +143,10 @@
                   << extraAttributes.toStdString() << std::endl;
 
         IntervalModel<Note>::toXml
-	    (out,
+            (out,
              indent,
-	     QString("%1 subtype=\"note\" valueQuantization=\"%2\"")
-	     .arg(extraAttributes).arg(m_valueQuantization));
+             QString("%1 subtype=\"note\" valueQuantization=\"%2\"")
+             .arg(extraAttributes).arg(m_valueQuantization));
     }
 
     /**
@@ -227,13 +227,13 @@
 
     NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
         
-	PointList points = getPoints(startFrame, endFrame);
+        PointList points = getPoints(startFrame, endFrame);
         NoteList notes;
 
         for (PointList::iterator pli =
-		 points.begin(); pli != points.end(); ++pli) {
+                 points.begin(); pli != points.end(); ++pli) {
 
-	    sv_frame_t duration = pli->duration;
+            sv_frame_t duration = pli->duration;
             if (duration == 0 || duration == 1) {
                 duration = sv_frame_t(getSampleRate() / 20);
             }
--- a/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -21,7 +21,7 @@
 
 ZoomLevel
 PowerOfSqrtTwoZoomConstraint::getNearestZoomLevel(ZoomLevel requested,
-						  RoundingDirection dir) const
+                                                  RoundingDirection dir) const
 {
     int type, power;
     int blockSize;
@@ -47,30 +47,30 @@
 
 int
 PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(int blockSize,
-						  int &type, 
-						  int &power,
-						  RoundingDirection dir) const
+                                                  int &type, 
+                                                  int &power,
+                                                  RoundingDirection dir) const
 {
 //    cerr << "given " << blockSize << endl;
 
     int minCachePower = getMinCachePower();
 
     if (blockSize < (1 << minCachePower)) {
-	type = -1;
-	power = 0;
-	float val = 1.0, prevVal = 1.0;
-	while (val + 0.01 < blockSize) {
-	    prevVal = val;
-	    val *= sqrtf(2.f);
-	}
-	int rval;
-	if (dir == RoundUp) rval = int(val + 0.01f);
-	else if (dir == RoundDown) rval = int(prevVal + 0.01f);
-	else if (val - float(blockSize) <
+        type = -1;
+        power = 0;
+        float val = 1.0, prevVal = 1.0;
+        while (val + 0.01 < blockSize) {
+            prevVal = val;
+            val *= sqrtf(2.f);
+        }
+        int rval;
+        if (dir == RoundUp) rval = int(val + 0.01f);
+        else if (dir == RoundDown) rval = int(prevVal + 0.01f);
+        else if (val - float(blockSize) <
                  float(blockSize) - prevVal) rval = int(val + 0.01f);
-	else rval = int(prevVal + 0.01);
-//	SVDEBUG << "returning " << rval << endl;
-	return rval;
+        else rval = int(prevVal + 0.01);
+//        SVDEBUG << "returning " << rval << endl;
+        return rval;
     }
 
     int prevBase = (1 << minCachePower);
@@ -81,46 +81,46 @@
 
     for (unsigned int i = 0; ; ++i) {
 
-	power = minCachePower + i/2;
-	type = i % 2;
+        power = minCachePower + i/2;
+        type = i % 2;
 
-	int base;
-	if (type == 0) {
-	    base = (1 << power);
-	} else {
-	    base = (((unsigned int)((1 << minCachePower) * sqrt(2.) + 0.01))
-		    << (power - minCachePower));
-	}
+        int base;
+        if (type == 0) {
+            base = (1 << power);
+        } else {
+            base = (((unsigned int)((1 << minCachePower) * sqrt(2.) + 0.01))
+                    << (power - minCachePower));
+        }
 
-//	SVDEBUG << "Testing base " << base << endl;
+//        SVDEBUG << "Testing base " << base << endl;
 
         if (base == blockSize) {
             result = base;
             break;
         }
 
-	if (base > blockSize) {
-	    if (dir == RoundNearest) {
-		if (base - blockSize < blockSize - prevBase) {
-		    dir = RoundUp;
-		} else {
-		    dir = RoundDown;
-		}
-	    }
-	    if (dir == RoundUp) {
-		result = base;
-		break;
-	    } else {
-		type = prevType;
-		power = prevPower;
-		result = prevBase;
-		break;
-	    }
-	}
+        if (base > blockSize) {
+            if (dir == RoundNearest) {
+                if (base - blockSize < blockSize - prevBase) {
+                    dir = RoundUp;
+                } else {
+                    dir = RoundDown;
+                }
+            }
+            if (dir == RoundUp) {
+                result = base;
+                break;
+            } else {
+                type = prevType;
+                power = prevPower;
+                result = prevBase;
+                break;
+            }
+        }
 
-	prevType = type;
-	prevPower = power;
-	prevBase = base;
+        prevType = type;
+        prevPower = power;
+        prevBase = base;
     }
 
     if (result > getMaxZoomLevel().level) {
--- a/data/model/PowerOfSqrtTwoZoomConstraint.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.h	Mon Sep 17 13:51:14 2018 +0100
@@ -31,7 +31,7 @@
                                     int &type,
                                     int &power,
                                     RoundingDirection dir = RoundNearest)
-	const;
+        const;
 };
 
 #endif
--- a/data/model/PowerOfTwoZoomConstraint.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/PowerOfTwoZoomConstraint.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -45,28 +45,28 @@
 
 int
 PowerOfTwoZoomConstraint::getNearestBlockSize(int req,
-					      RoundingDirection dir) const
+                                              RoundingDirection dir) const
 {
     int result = 0;
 
     for (int bs = 1; ; bs *= 2) {
-	if (bs >= req) {
-	    if (dir == RoundNearest) {
-		if (bs - req < req - bs/2) {
-		    result = bs;
-		    break;
-		} else {
-		    result = bs/2;
-		    break;
-		}
-	    } else if (dir == RoundDown) {
-		result = bs/2;
-		break;
-	    } else {
-		result = bs;
-		break;
-	    }
-	}
+        if (bs >= req) {
+            if (dir == RoundNearest) {
+                if (bs - req < req - bs/2) {
+                    result = bs;
+                    break;
+                } else {
+                    result = bs/2;
+                    break;
+                }
+            } else if (dir == RoundDown) {
+                result = bs/2;
+                break;
+            } else {
+                result = bs;
+                break;
+            }
+        }
     }
 
     if (result > getMaxZoomLevel().level) result = getMaxZoomLevel().level;
--- a/data/model/PowerOfTwoZoomConstraint.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/PowerOfTwoZoomConstraint.h	Mon Sep 17 13:51:14 2018 +0100
@@ -28,7 +28,7 @@
 protected:
     virtual int getNearestBlockSize(int requested,
                                     RoundingDirection dir = RoundNearest)
-	const;
+        const;
 };
 
 #endif
--- a/data/model/RangeSummarisableTimeValueModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/RangeSummarisableTimeValueModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_
-#define _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_
+#ifndef SV_RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H
+#define SV_RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H
 
 #include <QObject>
 
--- a/data/model/ReadOnlyWaveFileModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/ReadOnlyWaveFileModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -106,6 +106,10 @@
     if (m_fillThread) m_fillThread->wait();
     if (m_myReader) delete m_reader;
     m_reader = 0;
+
+    SVDEBUG << "ReadOnlyWaveFileModel: Destructor exiting; we had caches of "
+            << (m_cache[0].size() * sizeof(Range)) << " and "
+            << (m_cache[1].size() * sizeof(Range)) << " bytes" << endl;
 }
 
 bool
@@ -202,12 +206,18 @@
     return "";
 }
     
-vector<float>
-ReadOnlyWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
+floatvec_t
+ReadOnlyWaveFileModel::getData(int channel,
+                               sv_frame_t start,
+                               sv_frame_t count)
+    const
 {
-    // Read directly from the file.  This is used for e.g. audio
-    // playback or input to transforms.
+    // Read a single channel (if channel >= 0) or a mixdown of all
+    // channels (if channel == -1) directly from the file.  This is
+    // used for e.g. audio playback or input to transforms.
 
+    Profiler profiler("ReadOnlyWaveFileModel::getData");
+    
 #ifdef DEBUG_WAVE_FILE_MODEL
     cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << endl;
 #endif
@@ -215,7 +225,7 @@
     int channels = getChannelCount();
 
     if (channel >= channels) {
-        cerr << "ERROR: WaveFileModel::getData: channel ("
+        SVCERR << "ERROR: WaveFileModel::getData: channel ("
              << channel << ") >= channel count (" << channels << ")"
              << endl;
         return {};
@@ -236,12 +246,12 @@
         }
     }
 
-    vector<float> interleaved = m_reader->getInterleavedFrames(start, count);
+    floatvec_t interleaved = m_reader->getInterleavedFrames(start, count);
     if (channels == 1) return interleaved;
 
     sv_frame_t obtained = interleaved.size() / channels;
     
-    vector<float> result(obtained, 0.f);
+    floatvec_t result(obtained, 0.f);
     
     if (channel != -1) {
         // get a single channel
@@ -260,12 +270,14 @@
     return result;
 }
 
-vector<vector<float>>
+vector<floatvec_t>
 ReadOnlyWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
                                            sv_frame_t start, sv_frame_t count) const
 {
-    // Read directly from the file.  This is used for e.g. audio
-    // playback or input to transforms.
+    // Read a set of channels directly from the file.  This is used
+    // for e.g. audio playback or input to transforms.
+
+    Profiler profiler("ReadOnlyWaveFileModel::getMultiChannelData");
 
 #ifdef DEBUG_WAVE_FILE_MODEL
     cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << endl;
@@ -274,16 +286,18 @@
     int channels = getChannelCount();
 
     if (fromchannel > tochannel) {
-        cerr << "ERROR: ReadOnlyWaveFileModel::getData: fromchannel ("
-                  << fromchannel << ") > tochannel (" << tochannel << ")"
-                  << endl;
+        SVCERR << "ERROR: ReadOnlyWaveFileModel::getMultiChannelData: "
+               << "fromchannel (" << fromchannel
+               << ") > tochannel (" << tochannel << ")"
+               << endl;
         return {};
     }
 
     if (tochannel >= channels) {
-        cerr << "ERROR: ReadOnlyWaveFileModel::getData: tochannel ("
-                  << tochannel << ") >= channel count (" << channels << ")"
-                  << endl;
+        SVCERR << "ERROR: ReadOnlyWaveFileModel::getMultiChannelData: "
+               << "tochannel (" << tochannel
+               << ") >= channel count (" << channels << ")"
+               << endl;
         return {};
     }
 
@@ -304,11 +318,11 @@
         }
     }
 
-    vector<float> interleaved = m_reader->getInterleavedFrames(start, count);
+    floatvec_t 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));
+    vector<floatvec_t> result(reqchannels, floatvec_t(obtained, 0.f));
 
     for (int c = fromchannel; c <= tochannel; ++c) {
         int destc = c - fromchannel;
@@ -361,13 +375,13 @@
 
     if (cacheType != 0 && cacheType != 1) {
 
-	// We need to read directly from the file.  We haven't got
-	// this cached.  Hope the requested area is small.  This is
-	// not optimal -- we'll end up reading the same frames twice
-	// for stereo files, in two separate calls to this method.
-	// We could fairly trivially handle this for most cases that
-	// matter by putting a single cache in getInterleavedFrames
-	// for short queries.
+        // We need to read directly from the file.  We haven't got
+        // this cached.  Hope the requested area is small.  This is
+        // not optimal -- we'll end up reading the same frames twice
+        // for stereo files, in two separate calls to this method.
+        // We could fairly trivially handle this for most cases that
+        // matter by putting a single cache in getInterleavedFrames
+        // for short queries.
 
         m_directReadMutex.lock();
 
@@ -380,86 +394,86 @@
             m_lastDirectReadCount = count;
         }
 
-	float max = 0.0, min = 0.0, total = 0.0;
-	sv_frame_t i = 0, got = 0;
+        float max = 0.0, min = 0.0, total = 0.0;
+        sv_frame_t i = 0, got = 0;
 
-	while (i < count) {
+        while (i < count) {
 
-	    sv_frame_t index = i * channels + channel;
-	    if (index >= (sv_frame_t)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];
+            float sample = m_directRead[index];
             if (sample > max || got == 0) max = sample;
-	    if (sample < min || got == 0) min = sample;
+            if (sample < min || got == 0) min = sample;
             total += fabsf(sample);
 
-	    ++i;
+            ++i;
             ++got;
             
             if (got == blockSize) {
                 ranges.push_back(Range(min, max, total / float(got)));
                 min = max = total = 0.0f;
                 got = 0;
-	    }
-	}
+            }
+        }
 
         m_directReadMutex.unlock();
 
-	if (got > 0) {
+        if (got > 0) {
             ranges.push_back(Range(min, max, total / float(got)));
-	}
+        }
 
-	return;
+        return;
 
     } else {
 
-	QMutexLocker locker(&m_mutex);
+        QMutexLocker locker(&m_mutex);
     
-	const RangeBlock &cache = m_cache[cacheType];
+        const RangeBlock &cache = m_cache[cacheType];
 
         blockSize = roundedBlockSize;
 
-	sv_frame_t cacheBlock, div;
+        sv_frame_t cacheBlock, div;
 
         cacheBlock = (sv_frame_t(1) << m_zoomConstraint.getMinCachePower());
-	if (cacheType == 1) {
-	    cacheBlock = sv_frame_t(double(cacheBlock) * sqrt(2.) + 0.01);
-	}
+        if (cacheType == 1) {
+            cacheBlock = sv_frame_t(double(cacheBlock) * sqrt(2.) + 0.01);
+        }
         div = blockSize / cacheBlock;
 
-	sv_frame_t startIndex = start / cacheBlock;
-	sv_frame_t endIndex = (start + count) / cacheBlock;
+        sv_frame_t startIndex = start / cacheBlock;
+        sv_frame_t endIndex = (start + count) / cacheBlock;
 
-	float max = 0.0, min = 0.0, total = 0.0;
-	sv_frame_t i = 0, got = 0;
+        float max = 0.0, min = 0.0, total = 0.0;
+        sv_frame_t i = 0, got = 0;
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-	cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
+        cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
 #endif
 
-	for (i = 0; i <= endIndex - startIndex; ) {
+        for (i = 0; i <= endIndex - startIndex; ) {
         
-	    sv_frame_t index = (i + startIndex) * channels + channel;
-	    if (!in_range_for(cache, index)) break;
+            sv_frame_t index = (i + startIndex) * channels + channel;
+            if (!in_range_for(cache, index)) break;
             
             const Range &range = cache[index];
             if (range.max() > max || got == 0) max = range.max();
             if (range.min() < min || got == 0) min = range.min();
             total += range.absmean();
             
-	    ++i;
+            ++i;
             ++got;
             
-	    if (got == div) {
-		ranges.push_back(Range(min, max, total / float(got)));
+            if (got == div) {
+                ranges.push_back(Range(min, max, total / float(got)));
                 min = max = total = 0.0f;
                 got = 0;
-	    }
-	}
-		
-	if (got > 0) {
+            }
+        }
+                
+        if (got > 0) {
             ranges.push_back(Range(min, max, total / float(got)));
-	}
+        }
     }
 
 #ifdef DEBUG_WAVE_FILE_MODEL
@@ -544,19 +558,19 @@
 ReadOnlyWaveFileModel::fillTimerTimedOut()
 {
     if (m_fillThread) {
-	sv_frame_t fillExtent = m_fillThread->getFillExtent();
+        sv_frame_t fillExtent = m_fillThread->getFillExtent();
 #ifdef DEBUG_WAVE_FILE_MODEL
         SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl;
 #endif
-	if (fillExtent > m_lastFillExtent) {
-	    emit modelChangedWithin(m_lastFillExtent, fillExtent);
-	    m_lastFillExtent = fillExtent;
-	}
+        if (fillExtent > m_lastFillExtent) {
+            emit modelChangedWithin(m_lastFillExtent, fillExtent);
+            m_lastFillExtent = fillExtent;
+        }
     } else {
 #ifdef DEBUG_WAVE_FILE_MODEL
         SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: no thread" << endl;
 #endif
-	emit modelChanged();
+        emit modelChanged();
     }
 }
 
@@ -589,7 +603,7 @@
     
     sv_frame_t frame = 0;
     const sv_frame_t readBlockSize = 32768;
-    vector<float> block;
+    floatvec_t block;
 
     if (!m_model.isOK()) return;
     
@@ -643,7 +657,7 @@
             m_model.m_mutex.lock();
 
             for (sv_frame_t i = 0; i < gotBlockSize; ++i) {
-		
+                
                 for (int ch = 0; ch < channels; ++ch) {
 
                     sv_frame_t index = channels * i + ch;
--- a/data/model/ReadOnlyWaveFileModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/ReadOnlyWaveFileModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -36,8 +36,20 @@
     Q_OBJECT
 
 public:
+    /**
+     * Construct a WaveFileModel from a source path and optional
+     * resampling target rate
+     */
     ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate = 0);
+
+    /**
+     * Construct a WaveFileModel from a source path using an existing
+     * AudioFileReader. The model does not take ownership of the
+     * AudioFileReader, which remains managed by the caller and must
+     * outlive the model.
+     */
     ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader);
+    
     ~ReadOnlyWaveFileModel();
 
     bool isOK() const;
@@ -64,9 +76,9 @@
 
     void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; }
 
-    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
+    virtual floatvec_t 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 std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
 
     virtual int getSummaryBlockSize(int desired) const;
 
@@ -93,15 +105,15 @@
     {
     public:
         RangeCacheFillThread(ReadOnlyWaveFileModel &model) :
-	    m_model(model), m_fillExtent(0),
+            m_model(model), m_fillExtent(0),
             m_frameCount(model.getFrameCount()) { }
     
-	sv_frame_t getFillExtent() const { return m_fillExtent; }
+        sv_frame_t getFillExtent() const { return m_fillExtent; }
         virtual void run();
 
     protected:
         ReadOnlyWaveFileModel &m_model;
-	sv_frame_t m_fillExtent;
+        sv_frame_t m_fillExtent;
         sv_frame_t m_frameCount;
     };
          
@@ -122,7 +134,7 @@
     bool m_exiting;
     static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
 
-    mutable std::vector<float> m_directRead;
+    mutable floatvec_t m_directRead;
     mutable sv_frame_t m_lastDirectReadStart;
     mutable sv_frame_t m_lastDirectReadCount;
     mutable QMutex m_directReadMutex;
--- a/data/model/RegionModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/RegionModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -39,7 +39,7 @@
     RegionRec() : frame(0), value(0.f), duration(0) { }
     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) { }
+        frame(_frame), value(_value), duration(_duration), label(_label) { }
 
     int getDimensions() const { return 3; }
 
@@ -54,9 +54,9 @@
                QString indent = "",
                QString extraAttributes = "") const
     {
-	stream <<
+        stream <<
             QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" label=\"%5\" %6/>\n")
-	    .arg(indent).arg(frame).arg(value).arg(duration)
+            .arg(indent).arg(frame).arg(value).arg(duration)
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
@@ -71,20 +71,20 @@
     }
 
     struct Comparator {
-	bool operator()(const RegionRec &p1,
-			const RegionRec &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    if (p1.value != p2.value) return p1.value < p2.value;
-	    if (p1.duration != p2.duration) return p1.duration < p2.duration;
-	    return p1.label < p2.label;
-	}
+        bool operator()(const RegionRec &p1,
+                        const RegionRec &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            if (p1.value != p2.value) return p1.value < p2.value;
+            if (p1.duration != p2.duration) return p1.duration < p2.duration;
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const RegionRec &p1,
-			const RegionRec &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const RegionRec &p1,
+                        const RegionRec &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -96,8 +96,8 @@
 public:
     RegionModel(sv_samplerate_t sampleRate, int resolution,
                 bool notifyOnAdd = true) :
-	IntervalModel<RegionRec>(sampleRate, resolution, notifyOnAdd),
-	m_valueQuantization(0),
+        IntervalModel<RegionRec>(sampleRate, resolution, notifyOnAdd),
+        m_valueQuantization(0),
         m_haveDistinctValues(false)
     {
     }
@@ -105,10 +105,10 @@
     RegionModel(sv_samplerate_t sampleRate, int resolution,
                 float valueMinimum, float valueMaximum,
                 bool notifyOnAdd = true) :
-	IntervalModel<RegionRec>(sampleRate, resolution,
+        IntervalModel<RegionRec>(sampleRate, resolution,
                             valueMinimum, valueMaximum,
                             notifyOnAdd),
-	m_valueQuantization(0),
+        m_valueQuantization(0),
         m_haveDistinctValues(false)
     {
     }
@@ -132,10 +132,10 @@
                   << extraAttributes.toStdString() << std::endl;
 
         IntervalModel<RegionRec>::toXml
-	    (out,
+            (out,
              indent,
-	     QString("%1 subtype=\"region\" valueQuantization=\"%2\"")
-	     .arg(extraAttributes).arg(m_valueQuantization));
+             QString("%1 subtype=\"region\" valueQuantization=\"%2\"")
+             .arg(extraAttributes).arg(m_valueQuantization));
     }
 
     /**
--- a/data/model/SparseModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/SparseModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _SPARSE_MODEL_H_
-#define _SPARSE_MODEL_H_
+#ifndef SV_SPARSE_MODEL_H
+#define SV_SPARSE_MODEL_H
 
 #include "Model.h"
 #include "TabularModel.h"
@@ -43,9 +43,16 @@
 class SparseModel : public Model,
                     public TabularModel
 {
+    // If we omit the Q_OBJECT macro, lupdate complains.
+
+    // If we include it, moc fails (can't handle template classes).
+
+    // If we omit it, lupdate still seems to emit translatable
+    // messages for the tr() strings in here. So I guess we omit it.
+    
 public:
     SparseModel(sv_samplerate_t sampleRate, int resolution,
-		bool notifyOnAdd = true);
+                bool notifyOnAdd = true);
     virtual ~SparseModel() { }
     
     virtual bool isOK() const { return true; }
@@ -66,12 +73,12 @@
     // 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.
+    // end of the final point. (This is used by the Tony application)
     virtual void extendEndFrame(sv_frame_t to) { m_extendTo = to; }
-    
+
     typedef PointType Point;
     typedef std::multiset<PointType,
-			  typename PointType::OrderComparator> PointList;
+                          typename PointType::OrderComparator> PointList;
     typedef typename PointList::iterator PointListIterator;
     typedef typename PointList::const_iterator PointListConstIterator;
 
@@ -151,6 +158,8 @@
 
     virtual bool hasTextLabels() const { return m_hasTextLabels; }
 
+    virtual bool isSparse() const { return true; }
+
     QString getTypeName() const { return tr("Sparse"); }
 
     virtual QString getXmlOutputType() const { return "sparse"; }
@@ -168,7 +177,7 @@
                                                      DataExportOptions opts) const {
         return toDelimitedDataStringSubsetWithOptions
             (delimiter, opts,
-             std::min(getStartFrame(), sv_frame_t(0)), getEndFrame() + 1);
+             std::min(getStartFrame(), sv_frame_t(0)), getEndFrame());
     }
 
     virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const {
@@ -196,23 +205,23 @@
     class AddPointCommand : public Command
     {
     public:
-	AddPointCommand(SparseModel<PointType> *model,
-			const PointType &point,
+        AddPointCommand(SparseModel<PointType> *model,
+                        const PointType &point,
                         QString name = "") :
-	    m_model(model), m_point(point), m_name(name) { }
+            m_model(model), m_point(point), m_name(name) { }
 
-	virtual QString getName() const {
+        virtual QString getName() const {
             return (m_name == "" ? tr("Add Point") : m_name);
         }
 
-	virtual void execute() { m_model->addPoint(m_point); }
-	virtual void unexecute() { m_model->deletePoint(m_point); }
+        virtual void execute() { m_model->addPoint(m_point); }
+        virtual void unexecute() { m_model->deletePoint(m_point); }
 
-	const PointType &getPoint() const { return m_point; }
+        const PointType &getPoint() const { return m_point; }
 
     private:
-	SparseModel<PointType> *m_model;
-	PointType m_point;
+        SparseModel<PointType> *m_model;
+        PointType m_point;
         QString m_name;
     };
 
@@ -223,20 +232,20 @@
     class DeletePointCommand : public Command
     {
     public:
-	DeletePointCommand(SparseModel<PointType> *model,
-			   const PointType &point) :
-	    m_model(model), m_point(point) { }
+        DeletePointCommand(SparseModel<PointType> *model,
+                           const PointType &point) :
+            m_model(model), m_point(point) { }
 
-	virtual QString getName() const { return tr("Delete Point"); }
+        virtual QString getName() const { return tr("Delete Point"); }
 
-	virtual void execute() { m_model->deletePoint(m_point); }
-	virtual void unexecute() { m_model->addPoint(m_point); }
+        virtual void execute() { m_model->deletePoint(m_point); }
+        virtual void unexecute() { m_model->addPoint(m_point); }
 
-	const PointType &getPoint() const { return m_point; }
+        const PointType &getPoint() const { return m_point; }
 
     private:
-	SparseModel<PointType> *m_model;
-	PointType m_point;
+        SparseModel<PointType> *m_model;
+        PointType m_point;
     };
 
     
@@ -247,27 +256,27 @@
     class EditCommand : public MacroCommand
     {
     public:
-	EditCommand(SparseModel<PointType> *model, QString commandName);
+        EditCommand(SparseModel<PointType> *model, QString commandName);
 
-	virtual void addPoint(const PointType &point);
-	virtual void deletePoint(const PointType &point);
+        virtual void addPoint(const PointType &point);
+        virtual void deletePoint(const PointType &point);
 
-	/**
-	 * Stack an arbitrary other command in the same sequence.
-	 */
-	virtual void addCommand(Command *command) { addCommand(command, true); }
+        /**
+         * Stack an arbitrary other command in the same sequence.
+         */
+        virtual void addCommand(Command *command) { addCommand(command, true); }
 
-	/**
-	 * If any points have been added or deleted, return this
-	 * command (so the caller can add it to the command history).
-	 * Otherwise delete the command and return NULL.
-	 */
-	virtual EditCommand *finish();
+        /**
+         * If any points have been added or deleted, return this
+         * command (so the caller can add it to the command history).
+         * Otherwise delete the command and return NULL.
+         */
+        virtual EditCommand *finish();
 
     protected:
-	virtual void addCommand(Command *command, bool executeFirst);
+        virtual void addCommand(Command *command, bool executeFirst);
 
-	SparseModel<PointType> *m_model;
+        SparseModel<PointType> *m_model;
     };
 
 
@@ -277,27 +286,27 @@
     class RelabelCommand : public Command
     {
     public:
-	RelabelCommand(SparseModel<PointType> *model,
-		       const PointType &point,
-		       QString newLabel) :
-	    m_model(model), m_oldPoint(point), m_newPoint(point) {
-	    m_newPoint.label = newLabel;
-	}
+        RelabelCommand(SparseModel<PointType> *model,
+                       const PointType &point,
+                       QString newLabel) :
+            m_model(model), m_oldPoint(point), m_newPoint(point) {
+            m_newPoint.label = newLabel;
+        }
 
-	virtual QString getName() const { return tr("Re-Label Point"); }
+        virtual QString getName() const { return tr("Re-Label Point"); }
 
-	virtual void execute() { 
-	    m_model->deletePoint(m_oldPoint);
-	    m_model->addPoint(m_newPoint);
-	    std::swap(m_oldPoint, m_newPoint);
-	}
+        virtual void execute() { 
+            m_model->deletePoint(m_oldPoint);
+            m_model->addPoint(m_newPoint);
+            std::swap(m_oldPoint, m_newPoint);
+        }
 
-	virtual void unexecute() { execute(); }
+        virtual void unexecute() { execute(); }
 
     private:
-	SparseModel<PointType> *m_model;
-	PointType m_oldPoint;
-	PointType m_newPoint;
+        SparseModel<PointType> *m_model;
+        PointType m_oldPoint;
+        PointType m_newPoint;
     };
 
     /**
@@ -558,7 +567,7 @@
     QMutexLocker locker(&m_mutex);
     sv_frame_t f = 0;
     if (!m_points.empty()) {
-	f = m_points.begin()->frame;
+        f = m_points.begin()->frame;
     }
     return f;
 }
@@ -570,11 +579,14 @@
     QMutexLocker locker(&m_mutex);
     sv_frame_t f = 0;
     if (!m_points.empty()) {
-	PointListConstIterator i(m_points.end());
-	f = (--i)->frame;
+        PointListConstIterator i(m_points.end());
+        f = (--i)->frame + 1;
     }
-    if (m_extendTo > f) return m_extendTo;
-    else return f;
+    if (m_extendTo > f) {
+        return m_extendTo;
+    } else {
+        return f;
+    }
 }
 
 template <typename PointType>
@@ -618,7 +630,7 @@
     PointList rv;
 
     for (PointListConstIterator i = startItr; i != endItr; ++i) {
-	rv.insert(*i);
+        rv.insert(*i);
     }
 
     return rv;
@@ -634,7 +646,7 @@
     PointList rv;
 
     for (PointListConstIterator i = startItr; i != endItr; ++i) {
-	rv.insert(*i);
+        rv.insert(*i);
     }
 
     return rv;
@@ -704,9 +716,9 @@
     --i;
     sv_frame_t frame = i->frame;
     while (i->frame == frame) {
-	rv.insert(*i);
-	if (i == m_points.begin()) break;
-	--i;
+        rv.insert(*i);
+        if (i == m_points.begin()) break;
+        --i;
     }
 
     return rv;
@@ -726,8 +738,8 @@
 
     sv_frame_t frame = i->frame;
     while (i != m_points.end() && i->frame == frame) {
-	rv.insert(*i);
-	++i;
+        rv.insert(*i);
+        ++i;
     }
 
     return rv;
@@ -738,8 +750,8 @@
 SparseModel<PointType>::setResolution(int resolution)
 {
     {
-	QMutexLocker locker(&m_mutex);
-	m_resolution = resolution;
+        QMutexLocker locker(&m_mutex);
+        m_resolution = resolution;
         m_rows.clear();
     }
     emit modelChanged();
@@ -750,8 +762,8 @@
 SparseModel<PointType>::clear()
 {
     {
-	QMutexLocker locker(&m_mutex);
-	m_points.clear();
+        QMutexLocker locker(&m_mutex);
+        m_points.clear();
         m_pointCount = 0;
         m_rows.clear();
     }
@@ -762,30 +774,35 @@
 void
 SparseModel<PointType>::addPoint(const PointType &point)
 {
-    QMutexLocker locker(&m_mutex);
+    {
+        QMutexLocker locker(&m_mutex);
 
-    m_points.insert(point);
-    m_pointCount++;
-    if (point.getLabel() != "") m_hasTextLabels = true;
+        m_points.insert(point);
+        m_pointCount++;
+        if (point.getLabel() != "") m_hasTextLabels = true;
 
-    // Even though this model is nominally sparse, there may still be
-    // too many signals going on here (especially as they'll probably
-    // be queued from one thread to another), which is why we need the
-    // notifyOnAdd as an option rather than a necessity (the
-    // alternative is to notify on setCompletion).
+        // Even though this model is nominally sparse, there may still
+        // be too many signals going on here (especially as they'll
+        // probably be queued from one thread to another), which is
+        // why we need the notifyOnAdd as an option rather than a
+        // necessity (the alternative is to notify on setCompletion).
+
+        if (m_notifyOnAdd) {
+            m_rows.clear(); //!!! inefficient
+        } else {
+            if (m_sinceLastNotifyMin == -1 ||
+                point.frame < m_sinceLastNotifyMin) {
+                m_sinceLastNotifyMin = point.frame;
+            }
+            if (m_sinceLastNotifyMax == -1 ||
+                point.frame > m_sinceLastNotifyMax) {
+                m_sinceLastNotifyMax = point.frame;
+            }
+        }
+    }
 
     if (m_notifyOnAdd) {
-        m_rows.clear(); //!!! inefficient
-	emit modelChangedWithin(point.frame, point.frame + m_resolution);
-    } else {
-	if (m_sinceLastNotifyMin == -1 ||
-	    point.frame < m_sinceLastNotifyMin) {
-	    m_sinceLastNotifyMin = point.frame;
-	}
-	if (m_sinceLastNotifyMax == -1 ||
-	    point.frame > m_sinceLastNotifyMax) {
-	    m_sinceLastNotifyMax = point.frame;
-	}
+        emit modelChangedWithin(point.frame, point.frame + m_resolution);
     }
 }
 
@@ -812,23 +829,26 @@
 void
 SparseModel<PointType>::deletePoint(const PointType &point)
 {
-    QMutexLocker locker(&m_mutex);
+    {
+        QMutexLocker locker(&m_mutex);
 
-    PointListIterator i = m_points.lower_bound(point);
-    typename PointType::Comparator comparator;
-    while (i != m_points.end()) {
-        if (i->frame > point.frame) break;
-        if (!comparator(*i, point) && !comparator(point, *i)) {
-            m_points.erase(i);
-            m_pointCount--;
-            break;
-	    }
-        ++i;
-    }
+        PointListIterator i = m_points.lower_bound(point);
+        typename PointType::Comparator comparator;
+        while (i != m_points.end()) {
+            if (i->frame > point.frame) break;
+            if (!comparator(*i, point) && !comparator(point, *i)) {
+                m_points.erase(i);
+                m_pointCount--;
+                break;
+            }
+            ++i;
+        }
 
 //    std::cout << "SparseOneDimensionalModel: emit modelChanged("
-//	      << point.frame << ")" << std::endl;
-    m_rows.clear(); //!!! inefficient
+//              << point.frame << ")" << std::endl;
+        m_rows.clear(); //!!! inefficient
+    }
+    
     emit modelChangedWithin(point.frame, point.frame + m_resolution);
 }
 
@@ -837,36 +857,47 @@
 SparseModel<PointType>::setCompletion(int completion, bool update)
 {
 //    std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl;
+    bool emitCompletionChanged = true;
+    bool emitGeneralModelChanged = false;
+    bool emitRegionChanged = false;
 
-    QMutexLocker locker(&m_mutex);
+    {
+        QMutexLocker locker(&m_mutex);
 
-    if (m_completion != completion) {
-	m_completion = completion;
+        if (m_completion != completion) {
+            m_completion = completion;
 
-	if (completion == 100) {
+            if (completion == 100) {
 
-            if (!m_notifyOnAdd) {
-                emit completionChanged();
+                if (m_notifyOnAdd) {
+                    emitCompletionChanged = false;
+                }
+
+                m_notifyOnAdd = true; // henceforth
+                m_rows.clear(); //!!! inefficient
+                emitGeneralModelChanged = true;
+
+            } else if (!m_notifyOnAdd) {
+
+                if (update &&
+                    m_sinceLastNotifyMin >= 0 &&
+                    m_sinceLastNotifyMax >= 0) {
+                    m_rows.clear(); //!!! inefficient
+                    emitRegionChanged = true;
+                }
             }
+        }
+    }
 
-	    m_notifyOnAdd = true; // henceforth
-            m_rows.clear(); //!!! inefficient
-	    emit modelChanged();
-
-	} else if (!m_notifyOnAdd) {
-
-	    if (update &&
-                m_sinceLastNotifyMin >= 0 &&
-		m_sinceLastNotifyMax >= 0) {
-                m_rows.clear(); //!!! inefficient
-		emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
-		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
-	    } else {
-		emit completionChanged();
-	    }
-	} else {
-	    emit completionChanged();
-	}	    
+    if (emitCompletionChanged) {
+        emit completionChanged();
+    }
+    if (emitGeneralModelChanged) {
+        emit modelChanged();
+    }
+    if (emitRegionChanged) {
+        emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
+        m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
     }
 }
 
@@ -882,20 +913,20 @@
     QString type = getXmlOutputType();
 
     Model::toXml
-	(out,
+        (out,
          indent,
-	 QString("type=\"%1\" dimensions=\"%2\" resolution=\"%3\" notifyOnAdd=\"%4\" dataset=\"%5\" %6")
+         QString("type=\"%1\" dimensions=\"%2\" resolution=\"%3\" notifyOnAdd=\"%4\" dataset=\"%5\" %6")
          .arg(type)
-	 .arg(PointType(0).getDimensions())
-	 .arg(m_resolution)
-	 .arg(m_notifyOnAdd ? "true" : "false")
-	 .arg(getObjectExportId(&m_points))
-	 .arg(extraAttributes));
+         .arg(PointType(0).getDimensions())
+         .arg(m_resolution)
+         .arg(m_notifyOnAdd ? "true" : "false")
+         .arg(getObjectExportId(&m_points))
+         .arg(extraAttributes));
 
     out << indent;
     out << QString("<dataset id=\"%1\" dimensions=\"%2\">\n")
-	.arg(getObjectExportId(&m_points))
-	.arg(PointType(0).getDimensions());
+        .arg(getObjectExportId(&m_points))
+        .arg(PointType(0).getDimensions());
 
     for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) {
         i->toXml(out, indent + "  ");
@@ -942,24 +973,24 @@
 template <typename PointType>
 void
 SparseModel<PointType>::EditCommand::addCommand(Command *command,
-						bool executeFirst)
+                                                bool executeFirst)
 {
     if (executeFirst) command->execute();
 
     if (!m_commands.empty()) {
-	DeletePointCommand *dpc = dynamic_cast<DeletePointCommand *>(command);
-	if (dpc) {
-	    AddPointCommand *apc = dynamic_cast<AddPointCommand *>
-		(m_commands[m_commands.size() - 1]);
-	    typename PointType::Comparator comparator;
-	    if (apc) {
-		if (!comparator(apc->getPoint(), dpc->getPoint()) &&
-		    !comparator(dpc->getPoint(), apc->getPoint())) {
-		    deleteCommand(apc);
-		    return;
-		}
-	    }
-	}
+        DeletePointCommand *dpc = dynamic_cast<DeletePointCommand *>(command);
+        if (dpc) {
+            AddPointCommand *apc = dynamic_cast<AddPointCommand *>
+                (m_commands[m_commands.size() - 1]);
+            typename PointType::Comparator comparator;
+            if (apc) {
+                if (!comparator(apc->getPoint(), dpc->getPoint()) &&
+                    !comparator(dpc->getPoint(), apc->getPoint())) {
+                    deleteCommand(apc);
+                    return;
+                }
+            }
+        }
     }
 
     MacroCommand::addCommand(command);
--- a/data/model/SparseOneDimensionalModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/SparseOneDimensionalModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -41,7 +41,7 @@
                QString extraAttributes = "") const
     {
         stream << QString("%1<point frame=\"%2\" label=\"%3\" %4/>\n")
-	    .arg(indent).arg(frame).arg(XmlExportable::encodeEntities(label))
+            .arg(indent).arg(frame).arg(XmlExportable::encodeEntities(label))
             .arg(extraAttributes);
     }
 
@@ -54,18 +54,18 @@
     }
 
     struct Comparator {
-	bool operator()(const OneDimensionalPoint &p1,
-			const OneDimensionalPoint &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    return p1.label < p2.label;
-	}
+        bool operator()(const OneDimensionalPoint &p1,
+                        const OneDimensionalPoint &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const OneDimensionalPoint &p1,
-			const OneDimensionalPoint &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const OneDimensionalPoint &p1,
+                        const OneDimensionalPoint &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -77,10 +77,10 @@
     
 public:
     SparseOneDimensionalModel(sv_samplerate_t sampleRate, int resolution,
-			      bool notifyOnAdd = true) :
-	SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
+                              bool notifyOnAdd = true) :
+        SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
     {
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     virtual ~SparseOneDimensionalModel()
@@ -97,14 +97,14 @@
 
     int getIndexOf(const Point &point)
     {
-	// slow
-	int i = 0;
-	Point::Comparator comparator;
-	for (PointList::const_iterator j = m_points.begin();
-	     j != m_points.end(); ++j, ++i) {
-	    if (!comparator(*j, point) && !comparator(point, *j)) return i;
-	}
-	return -1;
+        // slow
+        int i = 0;
+        Point::Comparator comparator;
+        for (PointList::const_iterator j = m_points.begin();
+             j != m_points.end(); ++j, ++i) {
+            if (!comparator(*j, point) && !comparator(point, *j)) return i;
+        }
+        return -1;
     }
 
     QString getTypeName() const { return tr("Sparse 1-D"); }
@@ -189,11 +189,11 @@
 
     NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
         
-	PointList points = getPoints(startFrame, endFrame);
+        PointList points = getPoints(startFrame, endFrame);
         NoteList notes;
 
-	for (PointList::iterator pli =
-		 points.begin(); pli != points.end(); ++pli) {
+        for (PointList::iterator pli =
+                 points.begin(); pli != points.end(); ++pli) {
 
             notes.push_back
                 (NoteData(pli->frame,
--- a/data/model/SparseTimeValueModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/SparseTimeValueModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -31,7 +31,7 @@
 public:
     TimeValuePoint(sv_frame_t _frame) : frame(_frame), value(0.0f) { }
     TimeValuePoint(sv_frame_t _frame, float _value, QString _label) : 
-	frame(_frame), value(_value), label(_label) { }
+        frame(_frame), value(_value), label(_label) { }
 
     int getDimensions() const { return 2; }
     
@@ -45,7 +45,7 @@
                QString extraAttributes = "") const
     {
         stream << QString("%1<point frame=\"%2\" value=\"%3\" label=\"%4\" %5/>\n")
-	    .arg(indent).arg(frame).arg(value).arg(XmlExportable::encodeEntities(label))
+            .arg(indent).arg(frame).arg(value).arg(XmlExportable::encodeEntities(label))
             .arg(extraAttributes);
     }
 
@@ -59,19 +59,19 @@
     }
 
     struct Comparator {
-	bool operator()(const TimeValuePoint &p1,
-			const TimeValuePoint &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    if (p1.value != p2.value) return p1.value < p2.value;
-	    return p1.label < p2.label;
-	}
+        bool operator()(const TimeValuePoint &p1,
+                        const TimeValuePoint &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            if (p1.value != p2.value) return p1.value < p2.value;
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const TimeValuePoint &p1,
-			const TimeValuePoint &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const TimeValuePoint &p1,
+                        const TimeValuePoint &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -82,30 +82,30 @@
     
 public:
     SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
-			 bool notifyOnAdd = true) :
-	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
-					 notifyOnAdd)
+                         bool notifyOnAdd = true) :
+        SparseValueModel<TimeValuePoint>(sampleRate, resolution,
+                                         notifyOnAdd)
     {
         // Model is playable, but may not sound (if units not Hz or
         // range unsuitable)
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
-			 float valueMinimum, float valueMaximum,
-			 bool notifyOnAdd = true) :
-	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
-					 valueMinimum, valueMaximum,
-					 notifyOnAdd)
+                         float valueMinimum, float valueMaximum,
+                         bool notifyOnAdd = true) :
+        SparseValueModel<TimeValuePoint>(sampleRate, resolution,
+                                         valueMinimum, valueMaximum,
+                                         notifyOnAdd)
     {
         // Model is playable, but may not sound (if units not Hz or
         // range unsuitable)
-	PlayParameterRepository::getInstance()->addPlayable(this);
+        PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
     virtual ~SparseTimeValueModel()
     {
-	PlayParameterRepository::getInstance()->removePlayable(this);
+        PlayParameterRepository::getInstance()->removePlayable(this);
     }
 
     QString getTypeName() const { return tr("Sparse Time-Value"); }
--- a/data/model/SparseValueModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/SparseValueModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -32,19 +32,19 @@
 {
 public:
     SparseValueModel(sv_samplerate_t sampleRate, int resolution,
-		     bool notifyOnAdd = true) :
-	SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
-	m_valueMinimum(0.f),
-	m_valueMaximum(0.f),
+                     bool notifyOnAdd = true) :
+        SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
+        m_valueMinimum(0.f),
+        m_valueMaximum(0.f),
         m_haveExtents(false)
     { }
 
     SparseValueModel(sv_samplerate_t sampleRate, int resolution,
-		     float valueMinimum, float valueMaximum,
-		     bool notifyOnAdd = true) :
-	SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
-	m_valueMinimum(valueMinimum),
-	m_valueMaximum(valueMaximum),
+                     float valueMinimum, float valueMaximum,
+                     bool notifyOnAdd = true) :
+        SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
+        m_valueMinimum(valueMinimum),
+        m_valueMaximum(valueMaximum),
         m_haveExtents(true)
     { }
 
@@ -66,7 +66,7 @@
 
     virtual void addPoint(const PointType &point)
     {
-	bool allChange = false;
+        bool allChange = false;
 
         if (!ISNAN(point.value) && !ISINF(point.value)) {
             if (!m_haveExtents || point.value < m_valueMinimum) {
@@ -80,37 +80,37 @@
             m_haveExtents = true;
         }
 
-	SparseModel<PointType>::addPoint(point);
-	if (allChange) emit modelChanged();
+        SparseModel<PointType>::addPoint(point);
+        if (allChange) emit modelChanged();
     }
 
     virtual void deletePoint(const PointType &point)
     {
-	SparseModel<PointType>::deletePoint(point);
+        SparseModel<PointType>::deletePoint(point);
 
-	if (point.value == m_valueMinimum ||
-	    point.value == m_valueMaximum) {
+        if (point.value == m_valueMinimum ||
+            point.value == m_valueMaximum) {
 
-	    float formerMin = m_valueMinimum, formerMax = m_valueMaximum;
+            float formerMin = m_valueMinimum, formerMax = m_valueMaximum;
 
-	    for (typename SparseModel<PointType>::PointList::const_iterator i
-		     = m_points.begin();
-		 i != m_points.end(); ++i) {
+            for (typename SparseModel<PointType>::PointList::const_iterator i
+                     = m_points.begin();
+                 i != m_points.end(); ++i) {
 
-		if (i == m_points.begin() || i->value < m_valueMinimum) {
-		    m_valueMinimum = i->value;
+                if (i == m_points.begin() || i->value < m_valueMinimum) {
+                    m_valueMinimum = i->value;
 //                    std::cerr << "deletePoint: value min = " << m_valueMinimum << std::endl;
-		} 
-		if (i == m_points.begin() || i->value > m_valueMaximum) {
-		    m_valueMaximum = i->value;
+                } 
+                if (i == m_points.begin() || i->value > m_valueMaximum) {
+                    m_valueMaximum = i->value;
 //                    std::cerr << "deletePoint: value max = " << m_valueMaximum << std::endl;
-		} 
-	    }
+                } 
+            }
 
-	    if (formerMin != m_valueMinimum || formerMax != m_valueMaximum) {
-		emit modelChanged();
-	    }
-	}
+            if (formerMin != m_valueMinimum || formerMax != m_valueMaximum) {
+                emit modelChanged();
+            }
+        }
     }
 
     virtual void toXml(QTextStream &stream,
@@ -120,11 +120,11 @@
         std::cerr << "SparseValueModel::toXml: extraAttributes = \"" 
                   << extraAttributes.toStdString() << std::endl;
 
-	SparseModel<PointType>::toXml
-	    (stream,
+        SparseModel<PointType>::toXml
+            (stream,
              indent,
-	     QString("%1 minimum=\"%2\" maximum=\"%3\" units=\"%4\"")
-	     .arg(extraAttributes).arg(m_valueMinimum).arg(m_valueMaximum)
+             QString("%1 minimum=\"%2\" maximum=\"%3\" units=\"%4\"")
+             .arg(extraAttributes).arg(m_valueMinimum).arg(m_valueMaximum)
              .arg(this->encodeEntities(m_units)));
     }
 
--- a/data/model/TextModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/TextModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -33,7 +33,7 @@
 public:
     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) { }
+        frame(_frame), height(_height), label(_label) { }
 
     int getDimensions() const { return 2; }
     
@@ -46,8 +46,8 @@
     void toXml(QTextStream &stream, QString indent = "",
                QString extraAttributes = "") const
     {
-	stream << QString("%1<point frame=\"%2\" height=\"%3\" label=\"%4\" %5/>\n")
-	    .arg(indent).arg(frame).arg(height)
+        stream << QString("%1<point frame=\"%2\" height=\"%3\" label=\"%4\" %5/>\n")
+            .arg(indent).arg(frame).arg(height)
             .arg(encodeEntities(label)).arg(extraAttributes);
     }
 
@@ -61,19 +61,19 @@
     }
 
     struct Comparator {
-	bool operator()(const TextPoint &p1,
-			const TextPoint &p2) const {
-	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
-	    if (p1.height != p2.height) return p1.height < p2.height;
-	    return p1.label < p2.label;
-	}
+        bool operator()(const TextPoint &p1,
+                        const TextPoint &p2) const {
+            if (p1.frame != p2.frame) return p1.frame < p2.frame;
+            if (p1.height != p2.height) return p1.height < p2.height;
+            return p1.label < p2.label;
+        }
     };
     
     struct OrderComparator {
-	bool operator()(const TextPoint &p1,
-			const TextPoint &p2) const {
-	    return p1.frame < p2.frame;
-	}
+        bool operator()(const TextPoint &p1,
+                        const TextPoint &p2) const {
+            return p1.frame < p2.frame;
+        }
     };
 };
 
@@ -86,7 +86,7 @@
     
 public:
     TextModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
-	SparseModel<TextPoint>(sampleRate, resolution, notifyOnAdd)
+        SparseModel<TextPoint>(sampleRate, resolution, notifyOnAdd)
     { }
 
     virtual void toXml(QTextStream &out,
@@ -94,10 +94,10 @@
                        QString extraAttributes = "") const
     {
         SparseModel<TextPoint>::toXml
-	    (out, 
+            (out, 
              indent,
-	     QString("%1 subtype=\"text\"")
-	     .arg(extraAttributes));
+             QString("%1 subtype=\"text\"")
+             .arg(extraAttributes));
     }
 
     QString getTypeName() const { return tr("Text"); }
--- a/data/model/WritableWaveFileModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/WritableWaveFileModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -36,57 +36,121 @@
 
 //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1
 
-WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
-					     int channels,
-					     QString path) :
+WritableWaveFileModel::WritableWaveFileModel(QString path,
+                                             sv_samplerate_t sampleRate,
+                                             int channels,
+                                             Normalisation norm) :
     m_model(0),
-    m_writer(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
     m_reader(0),
+    m_normalisation(norm),
     m_sampleRate(sampleRate),
     m_channels(channels),
     m_frameCount(0),
     m_startFrame(0),
     m_proportion(PROPORTION_UNKNOWN)
 {
+    init(path);
+}
+
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
+                                             int channels,
+                                             Normalisation norm) :
+    m_model(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
+    m_reader(0),
+    m_normalisation(norm),
+    m_sampleRate(sampleRate),
+    m_channels(channels),
+    m_frameCount(0),
+    m_startFrame(0),
+    m_proportion(PROPORTION_UNKNOWN)
+{
+    init();
+}
+
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
+                                             int channels) :
+    m_model(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
+    m_reader(0),
+    m_normalisation(Normalisation::None),
+    m_sampleRate(sampleRate),
+    m_channels(channels),
+    m_frameCount(0),
+    m_startFrame(0),
+    m_proportion(PROPORTION_UNKNOWN)
+{
+    init();
+}
+
+void
+WritableWaveFileModel::init(QString path)
+{
     if (path.isEmpty()) {
         try {
+            // Temp dir is exclusive to this run of the application,
+            // so the filename only needs to be unique within that -
+            // model ID should be ok
             QDir dir(TempDirectory::getInstance()->getPath());
-            path = dir.filePath(QString("written_%1.wav")
-                                .arg((intptr_t)this));
-        } catch (DirectoryCreationFailed f) {
-            cerr << "WritableWaveFileModel: Failed to create temporary directory" << endl;
+            path = dir.filePath(QString("written_%1.wav").arg(getId()));
+        } catch (const DirectoryCreationFailed &f) {
+            SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl;
             return;
         }
     }
 
-    // Write directly to the target file, so that we can do
-    // incremental writes and concurrent reads
-    m_writer = new WavFileWriter(path, sampleRate, channels,
-                                 WavFileWriter::WriteToTarget);
-    if (!m_writer->isOK()) {
-        cerr << "WritableWaveFileModel: Error in creating WAV file writer: " << m_writer->getError() << endl;
-        delete m_writer; 
-        m_writer = 0;
+    m_targetPath = path;
+    m_temporaryPath = "";
+
+    // We don't delete or null-out writer/reader members after
+    // failures here - they are all deleted in the dtor, and the
+    // presence/existence of the model is what's used to determine
+    // whether to go ahead, not the writer/readers. If the model is
+    // non-null, then the necessary writer/readers must be OK, as the
+    // model is the last thing initialised
+    
+    m_targetWriter = new WavFileWriter(m_targetPath, m_sampleRate, m_channels,
+                                       WavFileWriter::WriteToTarget);
+    
+    if (!m_targetWriter->isOK()) {
+        SVCERR << "WritableWaveFileModel: Error in creating WAV file writer: " << m_targetWriter->getError() << endl;
         return;
     }
+    
+    if (m_normalisation != Normalisation::None) {
 
-    FileSource source(m_writer->getPath());
+        // Temp dir is exclusive to this run of the application, so
+        // the filename only needs to be unique within that
+        QDir dir(TempDirectory::getInstance()->getPath());
+        m_temporaryPath = dir.filePath(QString("prenorm_%1.wav").arg(getId()));
+
+        m_temporaryWriter = new WavFileWriter
+            (m_temporaryPath, m_sampleRate, m_channels,
+             WavFileWriter::WriteToTarget);
+    
+        if (!m_temporaryWriter->isOK()) {
+            SVCERR << "WritableWaveFileModel: Error in creating temporary WAV file writer: " << m_temporaryWriter->getError() << endl;
+            return;
+        }
+    }        
+
+    FileSource source(m_targetPath);
 
     m_reader = new WavFileReader(source, true);
     if (!m_reader->getError().isEmpty()) {
-        cerr << "WritableWaveFileModel: Error in creating wave file reader" << endl;
-        delete m_reader;
-        m_reader = 0;
+        SVCERR << "WritableWaveFileModel: Error in creating wave file reader: " << m_reader->getError() << endl;
         return;
     }
     
     m_model = new ReadOnlyWaveFileModel(source, m_reader);
     if (!m_model->isOK()) {
-        cerr << "WritableWaveFileModel: Error in creating wave file model" << endl;
+        SVCERR << "WritableWaveFileModel: Error in creating wave file model" << endl;
         delete m_model;
         m_model = 0;
-        delete m_reader;
-        m_reader = 0;
         return;
     }
     m_model->setStartFrame(m_startFrame);
@@ -99,7 +163,8 @@
 WritableWaveFileModel::~WritableWaveFileModel()
 {
     delete m_model;
-    delete m_writer;
+    delete m_targetWriter;
+    delete m_temporaryWriter;
     delete m_reader;
 }
 
@@ -107,49 +172,53 @@
 WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
 {
     m_startFrame = startFrame;
-    if (m_model) m_model->setStartFrame(startFrame);
+    if (m_model) {
+        m_model->setStartFrame(startFrame);
+    }
 }
 
 bool
-WritableWaveFileModel::addSamples(float **samples, sv_frame_t count)
+WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count)
 {
-    if (!m_writer) return false;
+    if (!m_model) return false;
 
 #ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
 //    SVDEBUG << "WritableWaveFileModel::addSamples(" << count << ")" << endl;
 #endif
 
-    if (!m_writer->writeSamples(samples, count)) {
-        cerr << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << m_writer->getError() << endl;
+    WavFileWriter *writer = m_targetWriter;
+    if (m_normalisation != Normalisation::None) {
+        writer = m_temporaryWriter;
+    }
+    
+    if (!writer->writeSamples(samples, count)) {
+        SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << writer->getError() << endl;
         return false;
     }
 
     m_frameCount += count;
 
-    static int updateCounter = 0;
-
-    if (m_reader && m_reader->getChannelCount() == 0) {
-#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
-        SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (initial)" << endl;
-#endif
-        m_reader->updateFrameCount();
-    } else if (++updateCounter == 100) {
-#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
-        SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (periodic)" << endl;
-#endif
-        if (m_reader) m_reader->updateFrameCount();
-        updateCounter = 0;
+    if (m_normalisation == Normalisation::None) {
+        if (m_reader->getChannelCount() == 0) {
+            m_reader->updateFrameCount();
+        }
     }
 
     return true;
 }
 
+void
+WritableWaveFileModel::updateModel()
+{
+    if (!m_model) return;
+    
+    m_reader->updateFrameCount();
+}
+
 bool
 WritableWaveFileModel::isOK() const
 {
-    bool ok = (m_writer && m_writer->isOK());
-//    SVDEBUG << "WritableWaveFileModel::isOK(): ok = " << ok << endl;
-    return ok;
+    return (m_model && m_model->isOK());
 }
 
 bool
@@ -176,11 +245,56 @@
 void
 WritableWaveFileModel::writeComplete()
 {
-    if (m_reader) m_reader->updateDone();
+    if (!m_model) return;
+
+    if (m_normalisation == Normalisation::None) {
+        m_targetWriter->close();
+    } else {
+        m_temporaryWriter->close();
+        normaliseToTarget();
+    }
+    
+    m_reader->updateDone();
     m_proportion = 100;
     emit modelChanged();
 }
 
+void
+WritableWaveFileModel::normaliseToTarget()
+{
+    if (m_temporaryPath == "") {
+        SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl;
+        return;
+    }
+    
+    WavFileReader normalisingReader(m_temporaryPath, false,
+                                    WavFileReader::Normalisation::Peak);
+
+    if (!normalisingReader.getError().isEmpty()) {
+        SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl;
+        return;
+    }
+
+    sv_frame_t frame = 0;
+    sv_frame_t block = 65536;
+    sv_frame_t count = normalisingReader.getFrameCount();
+
+    while (frame < count) {
+        auto frames = normalisingReader.getInterleavedFrames(frame, block);
+        if (!m_targetWriter->putInterleavedFrames(frames)) {
+            SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl;
+            return;
+        }
+        frame += block;
+    }
+
+    m_targetWriter->close();
+
+    delete m_temporaryWriter;
+    m_temporaryWriter = 0;
+    QFile::remove(m_temporaryPath);
+}
+
 sv_frame_t
 WritableWaveFileModel::getFrameCount() const
 {
@@ -188,14 +302,14 @@
     return m_frameCount;
 }
 
-vector<float>
+floatvec_t
 WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
 {
     if (!m_model || m_model->getChannelCount() == 0) return {};
     return m_model->getData(channel, start, count);
 }
 
-vector<vector<float>>
+vector<floatvec_t>
 WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
                                            sv_frame_t start, sv_frame_t count) const
 {
@@ -241,7 +355,7 @@
     Model::toXml
         (out, indent,
          QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
-         .arg(encodeEntities(m_writer->getPath()))
+         .arg(encodeEntities(m_targetPath))
          .arg(extraAttributes));
 }
 
--- a/data/model/WritableWaveFileModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/WritableWaveFileModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -28,18 +28,88 @@
     Q_OBJECT
 
 public:
-    WritableWaveFileModel(sv_samplerate_t sampleRate, int channels, QString path = "");
+    enum class Normalisation { None, Peak };
+
+    /**
+     * Create a WritableWaveFileModel of the given sample rate and
+     * channel count, storing data in a new float-type extended WAV
+     * file with the given path. If path is the empty string, the data
+     * will be stored in a newly-created temporary file.
+     *
+     * If normalisation == None, sample values will be written
+     * verbatim, and will be ready to read as soon as they have been
+     * written. Otherwise samples will be normalised on writing; this
+     * will require an additional pass and temporary file, and no
+     * samples will be available to read until after writeComplete()
+     * has returned.
+     */
+    WritableWaveFileModel(QString path,
+                          sv_samplerate_t sampleRate,
+                          int channels,
+                          Normalisation normalisation);
+    
+    /**
+     * Create a WritableWaveFileModel of the given sample rate and
+     * channel count, storing data in a new float-type extended WAV
+     * file in a temporary location. This is equivalent to passing an
+     * empty path to the constructor above.
+     *
+     * If normalisation == None, sample values will be written
+     * verbatim, and will be ready to read as soon as they have been
+     * written. Otherwise samples will be normalised on writing; this
+     * will require an additional pass and temporary file, and no
+     * samples will be available to read until after writeComplete()
+     * has returned.
+     */
+    WritableWaveFileModel(sv_samplerate_t sampleRate,
+                          int channels,
+                          Normalisation normalisation);
+
+    /**
+     * Create a WritableWaveFileModel of the given sample rate and
+     * channel count, storing data in a new float-type extended WAV
+     * file in a temporary location, and applying no normalisation.
+     *
+     * This is equivalent to passing an empty path and
+     * Normalisation::None to the first constructor above.
+     */
+    WritableWaveFileModel(sv_samplerate_t sampleRate,
+                          int channels);
+
     ~WritableWaveFileModel();
 
     /**
      * Call addSamples to append a block of samples to the end of the
-     * file.  Caller should also call setWriteProportion() to update
-     * the progress of this file, if it has a known end point, and
-     * should call writeComplete() when the file has been written.
+     * file.
+     *
+     * This function only appends the samples to the file being
+     * written; it does not update the model's view of the samples in
+     * that file. That is, it updates the file on disc but the model
+     * itself does not change its content. This is because re-reading
+     * the file to update the model may be more expensive than adding
+     * the samples in the first place. If you are writing small
+     * numbers of samples repeatedly, you probably only want the model
+     * to update periodically rather than after every write.
+     *
+     * Call updateModel() periodically to tell the model to update its
+     * own view of the samples in the file being written.
+     *
+     * Call setWriteProportion() periodically if the file being
+     * written has known duration and you want the model to be able to
+     * report the write progress as a percentage.
+     *
+     * Call writeComplete() when the file has been completely written.
      */
-    virtual bool addSamples(float **samples, sv_frame_t count);
+    virtual bool addSamples(const float *const *samples, sv_frame_t count);
 
     /**
+     * Tell the model to update its own (read) view of the (written)
+     * file. May cause modelChanged() and modelChangedWithin() to be
+     * emitted. See the comment to addSamples above for rationale.
+     */
+    void updateModel();
+    
+    /**
      * Set the proportion of the file which has been written so far,
      * as a percentage. This may be used to indicate progress.
      *
@@ -56,7 +126,7 @@
 
     /**
      * Indicate that writing is complete. You should call this even if
-     * you have never called setWriteProportion().
+     * you have never called setWriteProportion() or updateModel().
      */
     void writeComplete();
 
@@ -110,9 +180,9 @@
 
     void setStartFrame(sv_frame_t startFrame);
 
-    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
+    virtual floatvec_t 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 std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
 
     virtual int getSummaryBlockSize(int desired) const;
 
@@ -129,13 +199,35 @@
 
 protected:
     ReadOnlyWaveFileModel *m_model;
-    WavFileWriter *m_writer;
+
+    /** When normalising, this writer is used to write verbatim
+     *  samples to the temporary file prior to
+     *  normalisation. Otherwise it's null
+     */
+    WavFileWriter *m_temporaryWriter;
+    QString m_temporaryPath;
+
+    /** When not normalising, this writer is used to write verbatim
+     *  samples direct to the target file. When normalising, it is
+     *  used to write normalised samples to the target after the
+     *  temporary file has been completed. But it is still created on
+     *  initialisation, so that there is a file header ready for the
+     *  reader to address.
+     */
+    WavFileWriter *m_targetWriter;
+    QString m_targetPath;
+
     WavFileReader *m_reader;
+    Normalisation m_normalisation;
     sv_samplerate_t m_sampleRate;
     int m_channels;
     sv_frame_t m_frameCount;
     sv_frame_t m_startFrame;
     int m_proportion;
+
+private:
+    void init(QString path = "");
+    void normaliseToTarget();
 };
 
 #endif
--- a/data/model/test/Compares.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/test/Compares.h	Mon Sep 17 13:51:14 2018 +0100
@@ -24,12 +24,12 @@
         COMPARE_FUZZIER(a[cmp_i], n); \
     }
 
-#define COMPARE_ALL(a, b)						\
+#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)						\
+#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]); \
     }
@@ -39,12 +39,12 @@
         COMPARE_FUZZIER_F(a[cmp_i], n); \
     }
 
-#define COMPARE_ALL_F(a, b)						\
+#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)						\
+#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]); \
     }
--- a/data/model/test/MockWaveModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/test/MockWaveModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -22,25 +22,25 @@
 MockWaveModel::MockWaveModel(vector<Sort> sorts, int length, int pad)
 {
     for (auto sort: sorts) {
-	m_data.push_back(generate(sort, length, pad));
+        m_data.push_back(generate(sort, length, pad));
     }
 }
 
-vector<float>
+floatvec_t
 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;
+    floatvec_t 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;
+        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;
@@ -48,14 +48,14 @@
     return data;
 }
 
-vector<vector<float>>
+vector<floatvec_t>
 MockWaveModel::getMultiChannelData(int fromchannel, int tochannel,
-				   sv_frame_t start, sv_frame_t count) const
+                                   sv_frame_t start, sv_frame_t count) const
 {
-    vector<vector<float>> data(tochannel - fromchannel + 1);
+    vector<floatvec_t> data(tochannel - fromchannel + 1);
     
     for (int c = fromchannel; c <= tochannel; ++c) {
-        data.push_back(getData(c, start, count));
+        data[c] = getData(c, start, count);
     }
 
     return data;
@@ -72,17 +72,17 @@
     
     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;
-	}
+        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));
+        data.push_back(float(v));
     }
 
     for (int i = 0; i < pad; ++i) {
--- a/data/model/test/MockWaveModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/test/MockWaveModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -41,8 +41,8 @@
     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 floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const;
+    virtual std::vector<floatvec_t> 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 ""; }
--- a/data/model/test/TestFFTModel.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/test/TestFFTModel.h	Mon Sep 17 13:51:14 2018 +0100
@@ -66,18 +66,18 @@
                     float thresh = 1e-5f;
                     if (abs(reals[i] - eRe) > thresh ||
                         abs(imags[i] - eIm) > thresh) {
-                        cerr << "ERROR: output is not as expected for column "
+                        SVCERR << "ERROR: output is not as expected for column "
                              << i << " in channel " << ch << " (stepThrough = "
                              << stepThrough << ")" << endl;
-                        cerr << "expected : ";
+                        SVCERR << "expected : ";
                         for (int j = 0; j < hs1; ++j) {
-                            cerr << expectedValues[ch][j] << " ";
+                            SVCERR << expectedValues[ch][j] << " ";
                         }
-                        cerr << "\nactual   : ";
+                        SVCERR << "\nactual   : ";
                         for (int j = 0; j < hs1; ++j) {
-                            cerr << complex<float>(reals[j], imags[j]) << " ";
+                            SVCERR << complex<float>(reals[j], imags[j]) << " ";
                         }
-                        cerr << endl;
+                        SVCERR << endl;
                     }
                     COMPARE_FUZZIER_F(reals[i], eRe);
                     COMPARE_FUZZIER_F(imags[i], eIm);
@@ -101,7 +101,7 @@
     // are those of our expected signal.
     
     void dc_simple_rect() {
-	MockWaveModel mwm({ DC }, 16, 4);
+        MockWaveModel mwm({ DC }, 16, 4);
         test(&mwm, RectangularWindow, 8, 8, 8, 0,
              { { {}, {}, {}, {}, {} } }, 4);
         test(&mwm, RectangularWindow, 8, 8, 8, 1,
@@ -115,7 +115,7 @@
     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);
+        MockWaveModel mwm({ DC }, 16, 4);
         test(&mwm, HanningWindow, 8, 8, 8, 0,
              { { {}, {}, {}, {}, {} } }, 4);
         test(&mwm, HanningWindow, 8, 8, 8, 1,
@@ -127,7 +127,7 @@
     }
     
     void dc_simple_hann_halfoverlap() {
-	MockWaveModel mwm({ DC }, 16, 4);
+        MockWaveModel mwm({ DC }, 16, 4);
         test(&mwm, HanningWindow, 8, 4, 8, 0,
              { { {}, {}, {}, {}, {} } }, 7);
         test(&mwm, HanningWindow, 8, 4, 8, 2,
@@ -139,7 +139,7 @@
     }
     
     void sine_simple_rect() {
-	MockWaveModel mwm({ Sine }, 16, 4);
+        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
@@ -155,7 +155,7 @@
     }
     
     void cosine_simple_rect() {
-	MockWaveModel mwm({ Cosine }, 16, 4);
+        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,
@@ -169,7 +169,7 @@
     }
     
     void twochan_simple_rect() {
-	MockWaveModel mwm({ Sine, Cosine }, 16, 4);
+        MockWaveModel mwm({ Sine, Cosine }, 16, 4);
         // Test that the two channels are read and converted separately
         test(&mwm, RectangularWindow, 8, 8, 8, 0,
              {
@@ -194,7 +194,7 @@
     }
     
     void nyquist_simple_rect() {
-	MockWaveModel mwm({ Nyquist }, 16, 4);
+        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,
@@ -208,7 +208,7 @@
     }
     
     void dirac_simple_rect() {
-	MockWaveModel mwm({ Dirac }, 16, 4);
+        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,
@@ -222,7 +222,7 @@
     }
     
     void dirac_simple_rect_2() {
-	MockWaveModel mwm({ Dirac }, 16, 8);
+        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
@@ -239,7 +239,7 @@
     }
 
     void dirac_simple_rect_halfoverlap() {
-	MockWaveModel mwm({ Dirac }, 16, 4);
+        MockWaveModel mwm({ Dirac }, 16, 4);
         test(&mwm, RectangularWindow, 8, 4, 8, 0,
              { { {}, {}, {}, {}, {} } }, 7);
         test(&mwm, RectangularWindow, 8, 4, 8, 1,
--- a/data/model/test/svcore-data-model-test.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/test/svcore-data-model-test.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -24,21 +24,21 @@
     int good = 0, bad = 0;
 
     QCoreApplication app(argc, argv);
-    app.setOrganizationName("Sonic Visualiser");
+    app.setOrganizationName("sonic-visualiser");
     app.setApplicationName("test-model");
 
     {
-	TestFFTModel t;
-	if (QTest::qExec(&t, argc, argv) == 0) ++good;
-	else ++bad;
+        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;
+        SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+        return 1;
     } else {
-	cerr << "All tests passed" << endl;
-	return 0;
+        SVCERR << "All tests passed" << endl;
+        return 0;
     }
 }
 
--- a/data/osc/OSCQueue.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/osc/OSCQueue.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -34,7 +34,7 @@
 OSCQueue::oscError(int num, const char *msg, const char *path)
 {
     cerr << "ERROR: OSCQueue::oscError: liblo server error " << num
-	      << " in path " << path << ": " << msg << endl;
+              << " in path " << path << ": " << msg << endl;
 }
 
 int
@@ -48,7 +48,7 @@
     QString method;
 
     if (!queue->parseOSCPath(path, target, targetData, method)) {
-	return 1;
+        return 1;
     }
 
     OSCMessage message;
@@ -80,7 +80,7 @@
             break;
         }
 
-	++i;
+        ++i;
     }
 
     queue->postMessage(message);
@@ -190,7 +190,7 @@
                        QString &method)
 {
     while (path.startsWith("/")) {
-	path = path.right(path.length()-1);
+        path = path.right(path.length()-1);
     }
 
     int i = 0;
--- a/data/osc/demoscript.sh	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/osc/demoscript.sh	Mon Sep 17 13:51:14 2018 +0100
@@ -1,15 +1,16 @@
 #!/bin/bash
 
-audio=/share/music
+audio=/data/Music
 preferred=$audio/free
 list=audiofiles.txt
 used=audiofiles-used.txt
 
-df=vamp:vamp-aubio:aubioonset:detectionfunction
-#df=vamp:qm-vamp-plugins:qm-tempotracker:detection_fn
-onsets=vamp:vamp-aubio:aubioonset:onsets
-#onsets=vamp:qm-vamp-plugins:qm-tempotracker:beats
-beats=vamp:vamp-aubio:aubiotempo:beats
+#df=vamp:vamp-aubio:aubioonset:detectionfunction
+df=vamp:qm-vamp-plugins:qm-tempotracker:detection_fn
+#onsets=vamp:vamp-aubio:aubioonset:onsets
+onsets=vamp:vamp-example-plugins:percussiononsets:onsets
+beats=vamp:qm-vamp-plugins:qm-tempotracker:beats
+#beats=vamp:vamp-aubio:aubiotempo:beats
 #beats=$onsets
 #onsets=$beats
 chromagram=vamp:qm-vamp-plugins:qm-chromagram:chromagram
@@ -49,17 +50,27 @@
     echo "$file"
 }
 
+resize_normal() {
+#    sv-command resize 1000 500
+    sv-command resize 2000 1000
+}
+
+resize_big() {
+#    sv-command resize 1000 700
+    sv-command resize 2000 1400
+}
+
 load_a_file()
 {
     file=`pick_file`
     if ! sv-command open "$file"; then
 	pid="`pidof sonic-visualiser`"
 	if [ -z "$pid" ]; then
-	    ( setsid sonic-visualiser -geometry 1000x500+10+100 & )
+	    ( setsid sonic-visualiser -geometry +10+100 & )
 	    sleep 2
             #sudo renice +19 `pidof sonic-visualiser`
             #sudo renice +18 `pidof Xorg`
-            sv-command resize 1000 500
+            resize_normal
 	    load_a_file
 	else
 	    echo "ERROR: Unable to contact sonic-visualiser pid $pid" 1>&2
@@ -122,7 +133,7 @@
 fade_in()
 {
     sv-command set gain 0
-    sleep 0.5
+    sleep 1
     play "$@"
     for gain in 0.001 0.01 0.05 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1; do
 	sv-command set gain $gain
@@ -146,8 +157,8 @@
 #	sv-command set speedup "$speed"
 #	sleep 1
 #    done
-    for speed in -20 -100 -1000; do
-        sv-command set speedup "$speed"
+    for speed in 80 50 10; do
+        sv-command set speed "$speed"
         sleep 10
     done
 }
@@ -155,7 +166,7 @@
 stop()
 {
     sv-command stop "$@"
-    sv-command set speedup 0
+    sv-command set speed 100
 }
 
 quit()
@@ -285,7 +296,7 @@
     sv-command select 7.5 11
     fade_in selection
     sleep 10
-    sv-command set speedup -200
+    sv-command set speed 40
     sleep 10
     sv-command setcurrent 1
     sv-command delete pane
@@ -294,7 +305,7 @@
     sv-command set layer Normalize-Columns off
     sv-command set layer Normalize-Visible-Area on
     sleep 20
-    sv-command set speedup 0
+    sv-command set speed 100
     sleep 10
     sv-command select none
 #    fade_out
@@ -420,7 +431,7 @@
 #    reset
     sv-command set overlays 1
     sv-command set zoomwheels 0
-    sv-command resize 1000 500
+    resize_normal
     sv-command zoom default
     sv-command setcurrent 2
     sv-command delete pane
@@ -466,13 +477,13 @@
     sleep 4
     sv-command delete layer
     sleep 16
-    sv-command set speedup -50
+    sv-command set speed 66
     sleep 14
-    sv-command set speedup 50
+    sv-command set speed 150
     sleep 8
-    sv-command set speedup 100
+    sv-command set speed 200
     sleep 5
-    sv-command set speedup 200
+    sv-command set speed 400
     fade_out
 #    sleep 10
     sv-command select none
@@ -507,14 +518,14 @@
 load_a_file
 sv-command loop on
 
-sv-command resize 1000 500
+resize_normal
 show_stuff
 sleep 5
 sleep 20
 playback_bits
 
 #sleep 10
-sv-command resize 1000 700
+resize_big
 sv-command zoom default
 show_stuff
 onset_bits
@@ -524,7 +535,7 @@
 #sv-command resize 1000 700
 
 #sleep 10
-sv-command resize 1000 700
+resize_big
 #show_stuff
 spectrogram_bits
 
--- a/files.pri	Mon Dec 12 15:18:52 2016 +0000
+++ b/files.pri	Mon Sep 17 13:51:14 2018 +0100
@@ -1,6 +1,7 @@
 SVCORE_HEADERS = \
            base/AudioLevel.h \
            base/AudioPlaySource.h \
+           base/AudioRecordTarget.h \
            base/BaseTypes.h \
            base/Clipboard.h \
            base/ColumnOp.h \
@@ -23,8 +24,10 @@
            base/RangeMapper.h \
            base/RealTime.h \
            base/RecentFiles.h \
+           base/RecordDirectory.h \
            base/ResourceFinder.h \
            base/RingBuffer.h \
+           base/ScaleTickIntervals.h \
            base/Scavenger.h \
            base/Selection.h \
            base/Serialiser.h \
@@ -49,6 +52,7 @@
            data/fileio/CSVFileReader.h \
            data/fileio/CSVFileWriter.h \
            data/fileio/CSVFormat.h \
+           data/fileio/CSVStreamWriter.h \
            data/fileio/DataFileReader.h \
            data/fileio/DataFileReaderFactory.h \
            data/fileio/FileFinder.h \
@@ -59,14 +63,12 @@
            data/fileio/MP3FileReader.h \
            data/fileio/OggVorbisFileReader.h \
            data/fileio/PlaylistFileReader.h \
-           data/fileio/QuickTimeFileReader.h \
            data/fileio/CoreAudioFileReader.h \
            data/fileio/DecodingWavFileReader.h \
            data/fileio/WavFileReader.h \
            data/fileio/WavFileWriter.h \
            data/midi/MIDIEvent.h \
            data/midi/MIDIInput.h \
-           data/midi/rtmidi/RtError.h \
            data/midi/rtmidi/RtMidi.h \
            data/model/AggregateWaveModel.h \
            data/model/AlignmentModel.h \
@@ -107,6 +109,7 @@
            plugin/NativeVampPluginFactory.h \
            plugin/PiperVampPluginFactory.h \
            plugin/PluginIdentifier.h \
+           plugin/PluginPathSetter.h \
            plugin/PluginXml.h \
            plugin/RealTimePluginFactory.h \
            plugin/RealTimePluginInstance.h \
@@ -158,6 +161,7 @@
            base/RangeMapper.cpp \
            base/RealTimeSV.cpp \
            base/RecentFiles.cpp \
+           base/RecordDirectory.cpp \
            base/ResourceFinder.cpp \
            base/Selection.cpp \
            base/Serialiser.cpp \
@@ -188,7 +192,6 @@
            data/fileio/MP3FileReader.cpp \
            data/fileio/OggVorbisFileReader.cpp \
            data/fileio/PlaylistFileReader.cpp \
-           data/fileio/QuickTimeFileReader.cpp \
            data/fileio/CoreAudioFileReader.cpp \
            data/fileio/DecodingWavFileReader.cpp \
            data/fileio/WavFileReader.cpp \
@@ -220,6 +223,7 @@
            plugin/NativeVampPluginFactory.cpp \
            plugin/PiperVampPluginFactory.cpp \
            plugin/PluginIdentifier.cpp \
+           plugin/PluginPathSetter.cpp \
            plugin/PluginXml.cpp \
            plugin/RealTimePluginFactory.cpp \
            plugin/RealTimePluginInstance.cpp \
--- a/plugin/DSSIPluginFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/DSSIPluginFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -39,6 +39,7 @@
 #include "lrdf.h"
 #endif // HAVE_LRDF
 
+using std::string;
 
 DSSIPluginFactory::DSSIPluginFactory() :
     LADSPAPluginFactory()
@@ -61,61 +62,61 @@
     Profiler profiler("DSSIPluginFactory::enumeratePlugins");
 
     for (std::vector<QString>::iterator i = m_identifiers.begin();
-	 i != m_identifiers.end(); ++i) {
+         i != m_identifiers.end(); ++i) {
 
-	const DSSI_Descriptor *ddesc = getDSSIDescriptor(*i);
-	if (!ddesc) continue;
+        const DSSI_Descriptor *ddesc = getDSSIDescriptor(*i);
+        if (!ddesc) continue;
 
-	const LADSPA_Descriptor *descriptor = ddesc->LADSPA_Plugin;
-	if (!descriptor) continue;
-	
-//	SVDEBUG << "DSSIPluginFactory::enumeratePlugins: Name " << (descriptor->Name ? descriptor->Name : "NONE" ) << endl;
+        const LADSPA_Descriptor *descriptor = ddesc->LADSPA_Plugin;
+        if (!descriptor) continue;
+        
+//        SVDEBUG << "DSSIPluginFactory::enumeratePlugins: Name " << (descriptor->Name ? descriptor->Name : "NONE" ) << endl;
 
-	list.push_back(*i);
-	list.push_back(descriptor->Name);
-	list.push_back(QString("%1").arg(descriptor->UniqueID));
-	list.push_back(descriptor->Label);
-	list.push_back(descriptor->Maker);
-	list.push_back(descriptor->Copyright);
-	list.push_back((ddesc->run_synth || ddesc->run_multiple_synths) ? "true" : "false");
-	list.push_back(ddesc->run_multiple_synths ? "true" : "false");
-	list.push_back(m_taxonomy[*i]);
-	list.push_back(QString("%1").arg(descriptor->PortCount));
+        list.push_back(*i);
+        list.push_back(descriptor->Name);
+        list.push_back(QString("%1").arg(descriptor->UniqueID));
+        list.push_back(descriptor->Label);
+        list.push_back(descriptor->Maker);
+        list.push_back(descriptor->Copyright);
+        list.push_back((ddesc->run_synth || ddesc->run_multiple_synths) ? "true" : "false");
+        list.push_back(ddesc->run_multiple_synths ? "true" : "false");
+        list.push_back(m_taxonomy[*i]);
+        list.push_back(QString("%1").arg(descriptor->PortCount));
 
-	for (int p = 0; p < (int)descriptor->PortCount; ++p) {
+        for (int p = 0; p < (int)descriptor->PortCount; ++p) {
 
-	    int type = 0;
-	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
-		type |= PortType::Control;
-	    } else {
-		type |= PortType::Audio;
-	    }
-	    if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
-		type |= PortType::Input;
-	    } else {
-		type |= PortType::Output;
-	    }
+            int type = 0;
+            if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+                type |= PortType::Control;
+            } else {
+                type |= PortType::Audio;
+            }
+            if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+                type |= PortType::Input;
+            } else {
+                type |= PortType::Output;
+            }
 
-	    list.push_back(QString("%1").arg(p));
-	    list.push_back(descriptor->PortNames[p]);
-	    list.push_back(QString("%1").arg(type));
-	    list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
-	}
+            list.push_back(QString("%1").arg(p));
+            list.push_back(descriptor->PortNames[p]);
+            list.push_back(QString("%1").arg(type));
+            list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+        }
     }
 
     unloadUnusedLibraries();
 }
-	
+        
 RealTimePluginInstance *
 DSSIPluginFactory::instantiatePlugin(QString identifier,
-				     int instrument,
-				     int position,
-				     sv_samplerate_t sampleRate,
-				     int blockSize,
-				     int channels)
+                                     int instrument,
+                                     int position,
+                                     sv_samplerate_t sampleRate,
+                                     int blockSize,
+                                     int channels)
 {
     Profiler profiler("DSSIPluginFactory::instantiatePlugin");
 
@@ -123,14 +124,14 @@
 
     if (descriptor) {
 
-	DSSIPluginInstance *instance =
-	    new DSSIPluginInstance
-	    (this, instrument, identifier, position, sampleRate, blockSize, channels,
-	     descriptor);
+        DSSIPluginInstance *instance =
+            new DSSIPluginInstance
+            (this, instrument, identifier, position, sampleRate, blockSize, channels,
+             descriptor);
 
-	m_instances.insert(instance);
+        m_instances.insert(instance);
 
-	return instance;
+        return instance;
     }
 
     return 0;
@@ -143,49 +144,49 @@
     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
 
     if (soname == PluginIdentifier::BUILTIN_PLUGIN_SONAME) {
-	if (label == "sample_player") {
-	    const DSSI_Descriptor *descriptor = SamplePlayer::getDescriptor(0);
-	    if (descriptor) {
-		descriptor->receive_host_descriptor(&m_hostDescriptor);
-	    }
-	    return descriptor;
-	} else {
-	    return 0;
-	}
+        if (label == "sample_player") {
+            const DSSI_Descriptor *descriptor = SamplePlayer::getDescriptor(0);
+            if (descriptor) {
+                descriptor->receive_host_descriptor(&m_hostDescriptor);
+            }
+            return descriptor;
+        } else {
+            return 0;
+        }
     }
     
     bool firstInLibrary = false;
 
     if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
-	loadLibrary(soname);
-	if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
-	    cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: loadLibrary failed for " << soname << endl;
-	    return 0;
-	}
-	firstInLibrary = true;
+        loadLibrary(soname);
+        if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+            cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: loadLibrary failed for " << soname << endl;
+            return 0;
+        }
+        firstInLibrary = true;
     }
 
     void *libraryHandle = m_libraryHandles[soname];
 
     DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
-	DLSYM(libraryHandle, "dssi_descriptor");
+        DLSYM(libraryHandle, "dssi_descriptor");
 
     if (!fn) {
-	cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No descriptor function in library " << soname << endl;
-	return 0;
+        cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No descriptor function in library " << soname << endl;
+        return 0;
     }
 
     const DSSI_Descriptor *descriptor = 0;
     
     int index = 0;
     while ((descriptor = fn(index))) {
-	if (descriptor->LADSPA_Plugin->Label == label) {
-	    if (firstInLibrary && (descriptor->DSSI_API_Version >= 2)) {
-		descriptor->receive_host_descriptor(&m_hostDescriptor);
-	    }
-	    return descriptor;
-	}
-	++index;
+        if (descriptor->LADSPA_Plugin->Label == label) {
+            if (firstInLibrary && (descriptor->DSSI_API_Version >= 2)) {
+                descriptor->receive_host_descriptor(&m_hostDescriptor);
+            }
+            return descriptor;
+        }
+        ++index;
     }
 
     cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No such plugin as " << label << " in library " << soname << endl;
@@ -206,42 +207,42 @@
 DSSIPluginFactory::getPluginPath()
 {
     std::vector<QString> pathList;
-    std::string path;
+    string path;
 
-    char *cpath = getenv("DSSI_PATH");
-    if (cpath) path = cpath;
+    (void)getEnvUtf8("DSSI_PATH", path);
 
     if (path == "") {
 
         path = DEFAULT_DSSI_PATH;
 
-	char *home = getenv("HOME");
-	if (home) {
-            std::string::size_type f;
-            while ((f = path.find("$HOME")) != std::string::npos &&
+        string home;
+        if (getEnvUtf8("HOME", home)) {
+            string::size_type f;
+            while ((f = path.find("$HOME")) != string::npos &&
                    f < path.length()) {
                 path.replace(f, 5, home);
             }
         }
 
 #ifdef _WIN32
-        const char *pfiles = getenv("ProgramFiles");
-        if (!pfiles) pfiles = "C:\\Program Files";
-        {
-        std::string::size_type f;
-        while ((f = path.find("%ProgramFiles%")) != std::string::npos &&
+        string pfiles;
+        if (!getEnvUtf8("ProgramFiles", pfiles)) {
+            pfiles = "C:\\Program Files";
+        }
+
+        string::size_type f;
+        while ((f = path.find("%ProgramFiles%")) != string::npos &&
                f < path.length()) {
             path.replace(f, 14, pfiles);
         }
-        }
 #endif
     }
 
-    std::string::size_type index = 0, newindex = 0;
+    string::size_type index = 0, newindex = 0;
 
     while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) {
-	pathList.push_back(path.substr(index, newindex - index).c_str());
-	index = newindex + 1;
+        pathList.push_back(path.substr(index, newindex - index).c_str());
+        index = newindex + 1;
     }
     
     pathList.push_back(path.substr(index).c_str());
@@ -265,8 +266,8 @@
     lrdfPaths.push_back("/usr/share/ladspa/rdf");
 
     for (std::vector<QString>::iterator i = pathList.begin();
-	 i != pathList.end(); ++i) {
-	lrdfPaths.push_back(*i + "/rdf");
+         i != pathList.end(); ++i) {
+        lrdfPaths.push_back(*i + "/rdf");
     }
 
 #ifdef DSSI_BASE
@@ -300,11 +301,11 @@
     }
 
     DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
-	DLSYM(libraryHandle, "dssi_descriptor");
+        DLSYM(libraryHandle, "dssi_descriptor");
 
     if (!fn) {
-	cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No descriptor function in " << soname << endl;
-	return;
+        cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No descriptor function in " << soname << endl;
+        return;
     }
 
     const DSSI_Descriptor *descriptor = 0;
@@ -312,12 +313,12 @@
     int index = 0;
     while ((descriptor = fn(index))) {
 
-	const LADSPA_Descriptor *ladspaDescriptor = descriptor->LADSPA_Plugin;
-	if (!ladspaDescriptor) {
-	    cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No LADSPA descriptor for plugin " << index << " in " << soname << endl;
-	    ++index;
-	    continue;
-	}
+        const LADSPA_Descriptor *ladspaDescriptor = descriptor->LADSPA_Plugin;
+        if (!ladspaDescriptor) {
+            cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No LADSPA descriptor for plugin " << index << " in " << soname << endl;
+            ++index;
+            continue;
+        }
 
         RealTimePluginDescriptor *rtd = new RealTimePluginDescriptor;
         rtd->name = ladspaDescriptor->Name;
@@ -332,71 +333,71 @@
         rtd->audioOutputPortCount = 0;
         rtd->controlOutputPortCount = 0;
 
-	QString identifier = PluginIdentifier::createIdentifier
-	    ("dssi", soname, ladspaDescriptor->Label);
+        QString identifier = PluginIdentifier::createIdentifier
+            ("dssi", soname, ladspaDescriptor->Label);
 
 #ifdef HAVE_LRDF
-	char *def_uri = 0;
-	lrdf_defaults *defs = 0;
-		
-	QString category = m_taxonomy[identifier];
+        char *def_uri = 0;
+        lrdf_defaults *defs = 0;
+                
+        QString category = m_taxonomy[identifier];
 
         if (category == "" && m_lrdfTaxonomy[ladspaDescriptor->UniqueID] != "") {
             m_taxonomy[identifier] = m_lrdfTaxonomy[ladspaDescriptor->UniqueID];
             category = m_taxonomy[identifier];
         }
 
-	if (category == "") {
-	    std::string name = rtd->name;
-	    if (name.length() > 4 &&
-		name.substr(name.length() - 4) == " VST") {
-		if (descriptor->run_synth || descriptor->run_multiple_synths) {
-		    category = "VST instruments";
-		} else {
-		    category = "VST effects";
-		}
-		m_taxonomy[identifier] = category;
-	    }
-	}
+        if (category == "") {
+            string name = rtd->name;
+            if (name.length() > 4 &&
+                name.substr(name.length() - 4) == " VST") {
+                if (descriptor->run_synth || descriptor->run_multiple_synths) {
+                    category = "VST instruments";
+                } else {
+                    category = "VST effects";
+                }
+                m_taxonomy[identifier] = category;
+            }
+        }
 
         rtd->category = category.toStdString();
-	
-//	cerr << "Plugin id is " << ladspaDescriptor->UniqueID
+        
+//        cerr << "Plugin id is " << ladspaDescriptor->UniqueID
 //                  << ", identifier is \"" << identifier
-//		  << "\", category is \"" << category
-//		  << "\", name is " << ladspaDescriptor->Name
-//		  << ", label is " << ladspaDescriptor->Label
-//		  << endl;
-	
-	def_uri = lrdf_get_default_uri(ladspaDescriptor->UniqueID);
-	if (def_uri) {
-	    defs = lrdf_get_setting_values(def_uri);
-	}
-	
-	unsigned int controlPortNumber = 1;
-	
-	for (int i = 0; i < (int)ladspaDescriptor->PortCount; i++) {
-	    
-	    if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
-		
-		if (def_uri && defs) {
-		    
-		    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] =
-				defs->items[j].value;
-			}
-		    }
-		}
-		
-		++controlPortNumber;
-	    }
-	}
+//                  << "\", category is \"" << category
+//                  << "\", name is " << ladspaDescriptor->Name
+//                  << ", label is " << ladspaDescriptor->Label
+//                  << endl;
+        
+        def_uri = lrdf_get_default_uri(ladspaDescriptor->UniqueID);
+        if (def_uri) {
+            defs = lrdf_get_setting_values(def_uri);
+        }
+        
+        unsigned int controlPortNumber = 1;
+        
+        for (int i = 0; i < (int)ladspaDescriptor->PortCount; i++) {
+            
+            if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
+                
+                if (def_uri && defs) {
+                    
+                    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] =
+                                defs->items[j].value;
+                        }
+                    }
+                }
+                
+                ++controlPortNumber;
+            }
+        }
 #endif // HAVE_LRDF
 
-	for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) {
-	    if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
+        for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) {
+            if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
                 if (LADSPA_IS_PORT_INPUT(ladspaDescriptor->PortDescriptors[i])) {
                     ++rtd->parameterCount;
                 } else {
@@ -416,11 +417,13 @@
             }
         }
 
-	m_identifiers.push_back(identifier);
+        m_identifiers.push_back(identifier);
+
+        m_libraries[identifier] = soname;
 
         m_rtDescriptors[identifier] = rtd;
 
-	++index;
+        ++index;
     }
 
     if (DLCLOSE(libraryHandle) != 0) {
--- a/plugin/DSSIPluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/DSSIPluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -38,11 +38,13 @@
     virtual void enumeratePlugins(std::vector<QString> &list);
 
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
-						      int clientId,
-						      int position,
-						      sv_samplerate_t sampleRate,
-						      int blockSize,
-						      int channels);
+                                                      int clientId,
+                                                      int position,
+                                                      sv_samplerate_t sampleRate,
+                                                      int blockSize,
+                                                      int channels);
+
+    static std::vector<QString> getPluginPath();
 
 protected:
     DSSIPluginFactory();
@@ -52,8 +54,6 @@
         return PluginScan::DSSIPlugin;
     }
 
-    virtual std::vector<QString> getPluginPath();
-
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
 
     virtual void discoverPluginsFrom(QString soName);
--- a/plugin/DSSIPluginInstance.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/DSSIPluginInstance.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -54,13 +54,13 @@
 
 
 DSSIPluginInstance::DSSIPluginInstance(RealTimePluginFactory *factory,
-				       int clientId,
-				       QString identifier,
-				       int position,
-				       sv_samplerate_t sampleRate,
-				       int blockSize,
-				       int idealChannelCount,
-				       const DSSI_Descriptor* descriptor) :
+                                       int clientId,
+                                       QString identifier,
+                                       int position,
+                                       sv_samplerate_t sampleRate,
+                                       int blockSize,
+                                       int idealChannelCount,
+                                       const DSSI_Descriptor* descriptor) :
     RealTimePluginInstance(factory, identifier),
     m_client(clientId),
     m_position(position),
@@ -79,7 +79,7 @@
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::DSSIPluginInstance(" << identifier << ")"
-	      << endl;
+              << endl;
 #endif
 
     init();
@@ -88,10 +88,10 @@
     m_outputBuffers = new sample_t*[m_outputBufferCount];
 
     for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
-	m_inputBuffers[i] = new sample_t[blockSize];
+        m_inputBuffers[i] = new sample_t[blockSize];
     }
     for (int i = 0; i < m_outputBufferCount; ++i) {
-	m_outputBuffers[i] = new sample_t[blockSize];
+        m_outputBuffers[i] = new sample_t[blockSize];
     }
 
     m_ownBuffers = true;
@@ -100,9 +100,9 @@
 
     instantiate(sampleRate);
     if (isOK()) {
-	connectPorts();
-	activate();
-	initialiseGroupMembership();
+        connectPorts();
+        activate();
+        initialiseGroupMembership();
     }
 }
 
@@ -229,34 +229,34 @@
         {
             if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
                 m_audioPortsIn.push_back(i);
-	    } else {
+            } else {
                 m_audioPortsOut.push_back(i);
-	    }
+            }
         }
         else
         if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]))
         {
-	    if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+            if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
 
-		LADSPA_Data *data = new LADSPA_Data(0.0);
+                LADSPA_Data *data = new LADSPA_Data(0.0);
 
-		m_controlPortsIn.push_back(std::pair<long, LADSPA_Data*>
-					   (i, data));
+                m_controlPortsIn.push_back(std::pair<long, LADSPA_Data*>
+                                           (i, data));
 
-		m_backupControlPortsIn.push_back(0.0);
+                m_backupControlPortsIn.push_back(0.0);
 
-	    } else {
-		LADSPA_Data *data = new LADSPA_Data(0.0);
-		m_controlPortsOut.push_back(
+            } else {
+                LADSPA_Data *data = new LADSPA_Data(0.0);
+                m_controlPortsOut.push_back(
                     std::pair<long, LADSPA_Data*>(i, data));
-		if (!strcmp(descriptor->PortNames[i], "latency") ||
-		    !strcmp(descriptor->PortNames[i], "_latency")) {
+                if (!strcmp(descriptor->PortNames[i], "latency") ||
+                    !strcmp(descriptor->PortNames[i], "_latency")) {
 #ifdef DEBUG_DSSI
-		    cerr << "Wooo! We have a latency port!" << endl;
+                    cerr << "Wooo! We have a latency port!" << endl;
 #endif
-		    m_latencyPort = data;
-		}
-	    }
+                    m_latencyPort = data;
+                }
+            }
         }
 #ifdef DEBUG_DSSI
         else
@@ -279,7 +279,7 @@
 #endif
 
     if (m_latencyPort) {
-	if (!m_run) {
+        if (!m_run) {
             for (int i = 0; i < getAudioInputCount(); ++i) {
                 for (int j = 0; j < m_blockSize; ++j) {
                     m_inputBuffers[i][j] = 0.f;
@@ -287,7 +287,7 @@
             }
             run(Vamp::RealTime::zeroTime);
         }
-	latency = (sv_frame_t)(*m_latencyPort + 0.1);
+        latency = (sv_frame_t)(*m_latencyPort + 0.1);
     }
     
 #ifdef DEBUG_DSSI_PROCESS
@@ -301,8 +301,8 @@
 DSSIPluginInstance::silence()
 {
     if (m_instanceHandle != 0) {
-	deactivate();
-	activate();
+        deactivate();
+        activate();
     }
 }
 
@@ -317,41 +317,41 @@
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::setIdealChannelCount: channel count "
-	      << channels << " (was " << m_idealChannelCount << ")" << endl;
+              << channels << " (was " << m_idealChannelCount << ")" << endl;
 #endif
 
     if (channels == m_idealChannelCount) {
-	silence();
-	return;
+        silence();
+        return;
     }
 
     if (m_instanceHandle != 0) {
-	deactivate();
+        deactivate();
     }
 
     m_idealChannelCount = channels;
 
     if (channels > m_outputBufferCount) {
 
-	for (int i = 0; i < m_outputBufferCount; ++i) {
-	    delete[] m_outputBuffers[i];
-	}
+        for (int i = 0; i < m_outputBufferCount; ++i) {
+            delete[] m_outputBuffers[i];
+        }
 
-	delete[] m_outputBuffers;
+        delete[] m_outputBuffers;
 
-	m_outputBufferCount = channels;
+        m_outputBufferCount = channels;
 
-	m_outputBuffers = new sample_t*[m_outputBufferCount];
+        m_outputBuffers = new sample_t*[m_outputBufferCount];
 
-	for (int i = 0; i < m_outputBufferCount; ++i) {
-	    m_outputBuffers[i] = new sample_t[m_blockSize];
-	}
+        for (int i = 0; i < m_outputBufferCount; ++i) {
+            m_outputBuffers[i] = new sample_t[m_blockSize];
+        }
 
-	connectPorts();
+        connectPorts();
     }
 
     if (m_instanceHandle != 0) {
-	activate();
+        activate();
     }
 }
 
@@ -367,8 +367,8 @@
 DSSIPluginInstance::initialiseGroupMembership()
 {
     if (!m_descriptor->run_multiple_synths) {
-	m_grouped = false;
-	return;
+        m_grouped = false;
+        return;
     }
 
     //!!! GroupMap is not actually thread-safe.
@@ -377,24 +377,24 @@
 
     if (++pluginsInGroup > m_groupLocalEventBufferCount) {
 
-	size_t nextBufferCount = pluginsInGroup * 2;
+        size_t nextBufferCount = pluginsInGroup * 2;
 
-	snd_seq_event_t **eventLocalBuffers = new snd_seq_event_t *[nextBufferCount];
+        snd_seq_event_t **eventLocalBuffers = new snd_seq_event_t *[nextBufferCount];
 
-	for (size_t i = 0; i < m_groupLocalEventBufferCount; ++i) {
-	    eventLocalBuffers[i] = m_groupLocalEventBuffers[i];
-	}
-	for (size_t i = m_groupLocalEventBufferCount; i < nextBufferCount; ++i) {
-	    eventLocalBuffers[i] = new snd_seq_event_t[EVENT_BUFFER_SIZE];
-	}
+        for (size_t i = 0; i < m_groupLocalEventBufferCount; ++i) {
+            eventLocalBuffers[i] = m_groupLocalEventBuffers[i];
+        }
+        for (size_t i = m_groupLocalEventBufferCount; i < nextBufferCount; ++i) {
+            eventLocalBuffers[i] = new snd_seq_event_t[EVENT_BUFFER_SIZE];
+        }
 
-	if (m_groupLocalEventBuffers) {
-	    m_bufferScavenger.claim(new ScavengerArrayWrapper<snd_seq_event_t *>
-				    (m_groupLocalEventBuffers));
-	}
+        if (m_groupLocalEventBuffers) {
+            m_bufferScavenger.claim(new ScavengerArrayWrapper<snd_seq_event_t *>
+                                    (m_groupLocalEventBuffers));
+        }
 
-	m_groupLocalEventBuffers = eventLocalBuffers;
-	m_groupLocalEventBufferCount = nextBufferCount;
+        m_groupLocalEventBuffers = eventLocalBuffers;
+        m_groupLocalEventBufferCount = nextBufferCount;
     }
 
     m_grouped = true;
@@ -409,22 +409,22 @@
 
     if (m_threads.find(m_instanceHandle) != m_threads.end()) {
 
-	for (std::set<NonRTPluginThread *>::iterator i =
-		 m_threads[m_instanceHandle].begin();
-	     i != m_threads[m_instanceHandle].end(); ++i) {
+        for (std::set<NonRTPluginThread *>::iterator i =
+                 m_threads[m_instanceHandle].begin();
+             i != m_threads[m_instanceHandle].end(); ++i) {
 
-	    (*i)->setExiting();
-	    (*i)->wait();
-	    delete *i;
-	}
+            (*i)->setExiting();
+            (*i)->wait();
+            delete *i;
+        }
 
-	m_threads.erase(m_instanceHandle);
+        m_threads.erase(m_instanceHandle);
     }
 
     detachFromGroup();
 
     if (m_instanceHandle != 0) {
-	deactivate();
+        deactivate();
     }
 
     cleanup();
@@ -439,15 +439,15 @@
     m_controlPortsOut.clear();
 
     if (m_ownBuffers) {
-	for (int i = 0; i < getAudioInputCount(); ++i) {
-	    delete[] m_inputBuffers[i];
-	}
-	for (int i = 0; i < m_outputBufferCount; ++i) {
-	    delete[] m_outputBuffers[i];
-	}
+        for (int i = 0; i < getAudioInputCount(); ++i) {
+            delete[] m_inputBuffers[i];
+        }
+        for (int i = 0; i < m_outputBufferCount; ++i) {
+            delete[] m_outputBuffers[i];
+        }
 
-	delete[] m_inputBuffers;
-	delete[] m_outputBuffers;
+        delete[] m_inputBuffers;
+        delete[] m_outputBuffers;
     }
 
     m_audioPortsIn.clear();
@@ -467,10 +467,10 @@
     const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
 
     if (!descriptor->instantiate) {
-	cerr << "Bad plugin: plugin id " << descriptor->UniqueID
-		  << ":" << descriptor->Label
-		  << " has no instantiate method!" << endl;
-	return;
+        cerr << "Bad plugin: plugin id " << descriptor->UniqueID
+                  << ":" << descriptor->Label
+                  << " has no instantiate method!" << endl;
+        return;
     }
 
     unsigned long pluginRate = (unsigned long)(sampleRate);
@@ -483,24 +483,24 @@
 
     if (m_instanceHandle) {
 
-	if (m_descriptor->get_midi_controller_for_port) {
+        if (m_descriptor->get_midi_controller_for_port) {
 
-	    for (int i = 0; i < (int)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])) {
+                if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+                    LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
 
-		    int controller = m_descriptor->get_midi_controller_for_port
-			(m_instanceHandle, i);
+                    int controller = m_descriptor->get_midi_controller_for_port
+                        (m_instanceHandle, i);
 
-		    if (controller != 0 && controller != 32 &&
-			DSSI_IS_CC(controller)) {
+                    if (controller != 0 && controller != 32 &&
+                        DSSI_IS_CC(controller)) {
 
-			m_controllerMap[DSSI_CC_NUMBER(controller)] = i;
-		    }
-		}
-	    }
-	}
+                        m_controllerMap[DSSI_CC_NUMBER(controller)] = i;
+                    }
+                }
+            }
+        }
     }
 }
 
@@ -515,19 +515,19 @@
 #endif
 
     if (!m_descriptor || !m_descriptor->get_program) {
-	m_programCacheValid = true;
-	return;
+        m_programCacheValid = true;
+        return;
     }
 
     int index = 0;
     const DSSI_Program_Descriptor *programDescriptor;
     while ((programDescriptor = m_descriptor->get_program(m_instanceHandle, index))) {
-	++index;
-	ProgramDescriptor d;
-	d.bank = (int)programDescriptor->Bank;
-	d.program = (int)programDescriptor->Program;
-	d.name = programDescriptor->Name;
-	m_cachedPrograms.push_back(d);
+        ++index;
+        ProgramDescriptor d;
+        d.bank = (int)programDescriptor->Bank;
+        d.program = (int)programDescriptor->Program;
+        d.name = programDescriptor->Name;
+        m_cachedPrograms.push_back(d);
     }
 
 #ifdef DEBUG_DSSI
@@ -551,8 +551,8 @@
     ProgramList programs;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
-	 i != m_cachedPrograms.end(); ++i) {
-	programs.push_back(i->name);
+         i != m_cachedPrograms.end(); ++i) {
+        programs.push_back(i->name);
     }
 
     return programs;
@@ -570,8 +570,8 @@
     checkProgramCache();
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
-	 i != m_cachedPrograms.end(); ++i) {
-	if (i->bank == bank && i->program == program) return i->name;
+         i != m_cachedPrograms.end(); ++i) {
+        if (i->bank == bank && i->program == program) return i->name;
     }
 
     return std::string();
@@ -591,12 +591,12 @@
     int rv;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
-	 i != m_cachedPrograms.end(); ++i) {
-	if (i->name == name) {
-	    rv = i->bank;
-	    rv = (rv << 16) + i->program;
-	    return rv;
-	}
+         i != m_cachedPrograms.end(); ++i) {
+        if (i->name == name) {
+            rv = i->bank;
+            rv = (rv << 16) + i->program;
+            return rv;
+        }
     }
 
     return 0;
@@ -631,20 +631,20 @@
     int bankNo = 0, programNo = 0;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
-	 i != m_cachedPrograms.end(); ++i) {
+         i != m_cachedPrograms.end(); ++i) {
 
-	if (i->name == program) {
+        if (i->name == program) {
 
-	    bankNo = i->bank;
-	    programNo = i->program;
-	    found = true;
+            bankNo = i->bank;
+            programNo = i->program;
+            found = true;
 
 #ifdef DEBUG_DSSI
-	    SVDEBUG << "DSSIPluginInstance::selectProgram(" << program << "): found at bank " << bankNo << ", program " << programNo << endl;
+            SVDEBUG << "DSSIPluginInstance::selectProgram(" << program << "): found at bank " << bankNo << ", program " << programNo << endl;
 #endif
 
-	    break;
-	}
+            break;
+        }
     }
 
     if (!found) return;
@@ -660,9 +660,9 @@
 #endif
 
     if (backupPortValues) {
-	for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
-	    m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
-	}
+        for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+            m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+        }
     }
 }
 
@@ -678,16 +678,16 @@
 
     if (m_program != "") {
 #ifdef DEBUG_DSSI
-	SVDEBUG << "DSSIPluginInstance::activate: restoring program " << m_program << endl;
+        SVDEBUG << "DSSIPluginInstance::activate: restoring program " << m_program << endl;
 #endif
-	selectProgramAux(m_program, false);
+        selectProgramAux(m_program, false);
     }
 
     for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
 #ifdef DEBUG_DSSI
-	SVDEBUG << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << endl;
+        SVDEBUG << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << endl;
 #endif
-	*m_controlPortsIn[i].second = m_backupControlPortsIn[i];
+        *m_controlPortsIn[i].second = m_backupControlPortsIn[i];
     }
 }
 
@@ -697,8 +697,8 @@
     if (!m_descriptor || !m_descriptor->LADSPA_Plugin->connect_port) return;
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::connectPorts: " << getAudioInputCount() 
-	      << " audio ports in, " << m_audioPortsOut.size() << " out, "
-	      << m_outputBufferCount << " output buffers" << endl;
+              << " audio ports in, " << m_audioPortsOut.size() << " out, "
+              << m_outputBufferCount << " output buffers" << endl;
 #endif
 
     assert(sizeof(LADSPA_Data) == sizeof(float));
@@ -708,26 +708,26 @@
     int inbuf = 0, outbuf = 0;
 
     for (int i = 0; i < getAudioInputCount(); ++i) {
-	m_descriptor->LADSPA_Plugin->connect_port
-	    (m_instanceHandle,
-	     m_audioPortsIn[i],
-	     (LADSPA_Data *)m_inputBuffers[inbuf]);
-	++inbuf;
+        m_descriptor->LADSPA_Plugin->connect_port
+            (m_instanceHandle,
+             m_audioPortsIn[i],
+             (LADSPA_Data *)m_inputBuffers[inbuf]);
+        ++inbuf;
     }
 
     for (size_t i = 0; i < m_audioPortsOut.size(); ++i) {
-	m_descriptor->LADSPA_Plugin->connect_port
-	    (m_instanceHandle,
-	     m_audioPortsOut[i],
-	     (LADSPA_Data *)m_outputBuffers[outbuf]);
-	++outbuf;
+        m_descriptor->LADSPA_Plugin->connect_port
+            (m_instanceHandle,
+             m_audioPortsOut[i],
+             (LADSPA_Data *)m_outputBuffers[outbuf]);
+        ++outbuf;
     }
 
     for (size_t i = 0; i < m_controlPortsIn.size(); ++i) {
-	m_descriptor->LADSPA_Plugin->connect_port
-	    (m_instanceHandle,
-	     m_controlPortsIn[i].first,
-	     m_controlPortsIn[i].second);
+        m_descriptor->LADSPA_Plugin->connect_port
+            (m_instanceHandle,
+             m_controlPortsIn[i].first,
+             m_controlPortsIn[i].second);
 
         if (f) {
             float defaultValue = f->getPortDefault
@@ -741,10 +741,10 @@
     }
 
     for (size_t i = 0; i < m_controlPortsOut.size(); ++i) {
-	m_descriptor->LADSPA_Plugin->connect_port
-	    (m_instanceHandle,
-	     m_controlPortsOut[i].first,
-	     m_controlPortsOut[i].second);
+        m_descriptor->LADSPA_Plugin->connect_port
+            (m_instanceHandle,
+             m_controlPortsOut[i].first,
+             m_controlPortsOut[i].second);
     }
 }
 
@@ -766,12 +766,12 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	if (value < f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber)) {
-	    value = f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber);
-	}
-	if (value > f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber)) {
-	    value = f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber);
-	}
+        if (value < f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber)) {
+            value = f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber);
+        }
+        if (value > f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber)) {
+            value = f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber);
+        }
     }
 
     (*m_controlPortsIn[parameter].second) = value;
@@ -793,27 +793,27 @@
     float value = (float)cv;
 
     if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
-	if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
-	    /* unbounded: might as well leave the value alone. */
-	} else {
-	    /* bounded above only. just shift the range. */
-	    value = ub - 127.0f + value;
-	}
+        if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+            /* unbounded: might as well leave the value alone. */
+        } else {
+            /* bounded above only. just shift the range. */
+            value = ub - 127.0f + value;
+        }
     } else {
-	if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
-	    /* bounded below only. just shift the range. */
-	    value = lb + value;
-	} else {
-	    /* bounded both ends.  more interesting. */
-	    /* XXX !!! todo: fill in logarithmic, sample rate &c */
-	    value = lb + ((ub - lb) * value / 127.0f);
-	}
+        if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+            /* bounded below only. just shift the range. */
+            value = lb + value;
+        } else {
+            /* bounded both ends.  more interesting. */
+            /* XXX !!! todo: fill in logarithmic, sample rate &c */
+            value = lb + ((ub - lb) * value / 127.0f);
+        }
     }
 
     for (int i = 0; in_range_for(m_controlPortsIn, i); ++i) {
-	if (m_controlPortsIn[i].first == port) {
-	    setParameterValue(i, value);
-	}
+        if (m_controlPortsIn[i].first == port) {
+            setParameterValue(i, value);
+        }
     }
 }
 
@@ -841,10 +841,10 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	return f->getPortDefault(m_descriptor->LADSPA_Plugin,
-				 m_controlPortsIn[parameter].first);
+        return f->getPortDefault(m_descriptor->LADSPA_Plugin,
+                                 m_controlPortsIn[parameter].first);
     } else {
-	return 0.0f;
+        return 0.0f;
     }
 }
 
@@ -855,35 +855,35 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	return f->getPortDisplayHint(m_descriptor->LADSPA_Plugin,
+        return f->getPortDisplayHint(m_descriptor->LADSPA_Plugin,
                                      m_controlPortsIn[parameter].first);
     } else {
-	return PortHint::NoHint;
+        return PortHint::NoHint;
     }
 }
 
 std::string
 DSSIPluginInstance::configure(std::string key,
-			      std::string value)
+                              std::string value)
 {
     if (!m_descriptor || !m_descriptor->configure) return std::string();
 
     if (key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY.toStdString()) {
 #ifdef DSSI_PROJECT_DIRECTORY_KEY
-	key = DSSI_PROJECT_DIRECTORY_KEY;
+        key = DSSI_PROJECT_DIRECTORY_KEY;
 #else
-	return std::string();
+        return std::string();
 #endif
     }
-	
+        
     
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::configure(" << key << "," << value << ")" << endl;
 #endif
 
     char *message = m_descriptor->configure(m_instanceHandle,
-					    key.c_str(),
-					    value.c_str());
+                                            key.c_str(),
+                                            value.c_str());
 
     m_programCacheValid = false;
 
@@ -895,16 +895,16 @@
     // as project directory
 #ifdef DSSI_RESERVED_CONFIGURE_PREFIX
     if (QString(key.c_str()).startsWith(DSSI_RESERVED_CONFIGURE_PREFIX)) {
-	return qm;
+        return qm;
     }
 #endif
 
     if (message) {
-	if (m_descriptor->LADSPA_Plugin && m_descriptor->LADSPA_Plugin->Label) {
-	    qm = std::string(m_descriptor->LADSPA_Plugin->Label) + ": ";
-	}
-	qm = qm + message;
-	free(message);
+        if (m_descriptor->LADSPA_Plugin && m_descriptor->LADSPA_Plugin->Label) {
+            qm = std::string(m_descriptor->LADSPA_Plugin->Label) + ": ";
+        }
+        qm = qm + message;
+        free(message);
 
         cerr << "DSSIPluginInstance::configure: warning: configure returned message: \"" << qm << "\"" << endl;
     }
@@ -914,7 +914,7 @@
 
 void
 DSSIPluginInstance::sendEvent(const RealTime &eventTime,
-			      const void *e)
+                              const void *e)
 {
 #ifdef DEBUG_DSSI_PROCESS
     SVDEBUG << "DSSIPluginInstance::sendEvent: last was " << m_lastEventSendTime << " (valid " << m_haveLastEventSendTime << "), this is " << eventTime << endl;
@@ -925,12 +925,12 @@
     // we will happily drop events here if we find the timeline going
     // backwards.
     if (m_haveLastEventSendTime &&
-	m_lastEventSendTime > eventTime) {
+        m_lastEventSendTime > eventTime) {
 #ifdef DEBUG_DSSI_PROCESS
-	cerr << "... clearing down" << endl;
+        cerr << "... clearing down" << endl;
 #endif
-	m_haveLastEventSendTime = false;
-	clearEvents();
+        m_haveLastEventSendTime = false;
+        clearEvents();
     }
 
     snd_seq_event_t *event = (snd_seq_event_t *)e;
@@ -968,21 +968,21 @@
 #endif
 
     if (controller == 0) { // bank select MSB
-	
-	m_pending.msb = ev->data.control.value;
+        
+        m_pending.msb = ev->data.control.value;
 
     } else if (controller == 32) { // bank select LSB
 
-	m_pending.lsb = ev->data.control.value;
+        m_pending.lsb = ev->data.control.value;
 
     } else if (controller > 0 && controller < 128) {
-	
-	if (m_controllerMap.find(controller) != m_controllerMap.end()) {
-	    int port = m_controllerMap[controller];
-	    setPortValueFromController(port, ev->data.control.value);
-	} else {
-	    return true; // pass through to plugin
-	}
+        
+        if (m_controllerMap.find(controller) != m_controllerMap.end()) {
+            int port = m_controllerMap[controller];
+            setPortValueFromController(port, ev->data.control.value);
+        } else {
+            return true; // pass through to plugin
+        }
     }
 
     return false;
@@ -1000,32 +1000,32 @@
     if (m_descriptor && m_descriptor->select_program) needLock = true;
 
     if (needLock) {
-	if (!m_processLock.tryLock()) {
-	    for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
-		memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
-	    }
-	    return;
-	}
+        if (!m_processLock.tryLock()) {
+            for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+                memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+            }
+            return;
+        }
     }
 
     if (m_grouped) {
-	runGrouped(blockTime);
-	goto done;
+        runGrouped(blockTime);
+        goto done;
     }
 
     if (!m_descriptor || !m_descriptor->run_synth) {
-	m_eventBuffer.skip(m_eventBuffer.getReadSpace());
-	m_haveLastEventSendTime = false;
-	if (m_descriptor && m_descriptor->LADSPA_Plugin->run) {
-	    m_descriptor->LADSPA_Plugin->run(m_instanceHandle, count);
-	} else {
-	    for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
-		memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
-	    }
-	}
-	m_run = true;
-	if (needLock) m_processLock.unlock();
-	return;
+        m_eventBuffer.skip(m_eventBuffer.getReadSpace());
+        m_haveLastEventSendTime = false;
+        if (m_descriptor && m_descriptor->LADSPA_Plugin->run) {
+            m_descriptor->LADSPA_Plugin->run(m_instanceHandle, count);
+        } else {
+            for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+                memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+            }
+        }
+        m_run = true;
+        if (needLock) m_processLock.unlock();
+        return;
     }
 
 #ifdef DEBUG_DSSI_PROCESS
@@ -1034,65 +1034,65 @@
 
 #ifdef DEBUG_DSSI_PROCESS
     if (m_eventBuffer.getReadSpace() > 0) {
-	SVDEBUG << "DSSIPluginInstance::run: event buffer has "
-		  << m_eventBuffer.getReadSpace() << " event(s) in it" << endl;
+        SVDEBUG << "DSSIPluginInstance::run: event buffer has "
+                  << m_eventBuffer.getReadSpace() << " event(s) in it" << endl;
     }
 #endif
 
     while (m_eventBuffer.getReadSpace() > 0) {
 
-	snd_seq_event_t *ev = localEventBuffer + evCount;
-	*ev = m_eventBuffer.peekOne();
-	bool accept = true;
+        snd_seq_event_t *ev = localEventBuffer + evCount;
+        *ev = m_eventBuffer.peekOne();
+        bool accept = true;
 
-	RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+        RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
 
         sv_frame_t frameOffset = 0;
-	if (evTime > blockTime) {
-	    frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
-	}
+        if (evTime > blockTime) {
+            frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+        }
 
 #ifdef DEBUG_DSSI_PROCESS
-	SVDEBUG << "DSSIPluginInstance::run: evTime " << evTime << ", blockTime " << blockTime << ", frameOffset " << frameOffset
-		  << ", blockSize " << m_blockSize << endl;
-	cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << endl;
+        SVDEBUG << "DSSIPluginInstance::run: evTime " << evTime << ", blockTime " << blockTime << ", frameOffset " << frameOffset
+                  << ", blockSize " << m_blockSize << endl;
+        cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << endl;
 #endif
 
-	if (frameOffset >= (long)count) break;
-	if (frameOffset < 0) {
-	    frameOffset = 0;
-	    if (ev->type == SND_SEQ_EVENT_NOTEON) {
-		m_eventBuffer.skip(1);
-		continue;
-	    }
-	}
+        if (frameOffset >= (long)count) break;
+        if (frameOffset < 0) {
+            frameOffset = 0;
+            if (ev->type == SND_SEQ_EVENT_NOTEON) {
+                m_eventBuffer.skip(1);
+                continue;
+            }
+        }
 
-	ev->time.tick = (snd_seq_tick_time_t)frameOffset;
-	m_eventBuffer.skip(1);
+        ev->time.tick = (snd_seq_tick_time_t)frameOffset;
+        m_eventBuffer.skip(1);
 
-	if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
-	    accept = handleController(ev);
-	} else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
-	    m_pending.program = ev->data.control.value;
-	    accept = false;
-	}
+        if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+            accept = handleController(ev);
+        } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+            m_pending.program = ev->data.control.value;
+            accept = false;
+        }
 
-	if (accept) {
-	    if (++evCount >= EVENT_BUFFER_SIZE) break;
-	}
+        if (accept) {
+            if (++evCount >= EVENT_BUFFER_SIZE) break;
+        }
     }
 
     if (m_pending.program >= 0 && m_descriptor->select_program) {
 
-	int program = m_pending.program;
-	int bank = m_pending.lsb + 128 * m_pending.msb;
+        int program = m_pending.program;
+        int bank = m_pending.lsb + 128 * m_pending.msb;
 
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::run: making select_program(" << bank << "," << program << ") call" << endl;
 #endif
 
-	m_pending.lsb = m_pending.msb = m_pending.program = -1;
-	m_descriptor->select_program(m_instanceHandle, bank, program);
+        m_pending.lsb = m_pending.msb = m_pending.program = -1;
+        m_descriptor->select_program(m_instanceHandle, bank, program);
 
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::run: made select_program(" << bank << "," << program << ") call" << endl;
@@ -1101,16 +1101,16 @@
 
 #ifdef DEBUG_DSSI_PROCESS
     SVDEBUG << "DSSIPluginInstance::run: running with " << evCount << " events"
-	      << endl;
+              << endl;
 #endif
 
     m_descriptor->run_synth(m_instanceHandle, count,
-			    localEventBuffer, evCount);
+                            localEventBuffer, evCount);
 
 #ifdef DEBUG_DSSI_PROCESS
 //    for (int i = 0; i < count; ++i) {
-//	cout << m_outputBuffers[0][i] << " ";
-//	if (i % 8 == 0) cout << endl;
+//        cout << m_outputBuffers[0][i] << " ";
+//        if (i % 8 == 0) cout << endl;
 //    }
 #endif
 
@@ -1120,31 +1120,31 @@
     int numAudioOuts = int(m_audioPortsOut.size());
     
     if (numAudioOuts == 0) {
-	// copy inputs to outputs
-	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];
-	    }
-	}
+        // copy inputs to outputs
+        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 < numAudioOuts) {
-	if (m_idealChannelCount == 1) {
-	    // mix down to mono
-	    for (int ch = 1; ch < numAudioOuts; ++ch) {
-		for (int i = 0; i < m_blockSize; ++i) {
-		    m_outputBuffers[0][i] += m_outputBuffers[ch][i];
-		}
-	    }
-	}
+        if (m_idealChannelCount == 1) {
+            // mix down to mono
+            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 > numAudioOuts) {
-	// duplicate
-	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];
-	    }
-	}
-    }	
+        // duplicate
+        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];
+            }
+        }
+    }        
 
     m_lastRunTime = blockTime;
     m_run = true;
@@ -1168,22 +1168,22 @@
 #endif
 
     if (m_lastRunTime != blockTime) {
-	for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
-	    DSSIPluginInstance *instance = *i;
-	    if (instance != this && instance->m_lastRunTime == blockTime) {
+        for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
+            DSSIPluginInstance *instance = *i;
+            if (instance != this && instance->m_lastRunTime == blockTime) {
 #ifdef DEBUG_DSSI_PROCESS
-		SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): plugin " << instance << " has already been run" << endl;
+                SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): plugin " << instance << " has already been run" << endl;
 #endif
-		needRun = false;
-	    }
-	}
+                needRun = false;
+            }
+        }
     }
 
     if (!needRun) {
 #ifdef DEBUG_DSSI_PROCESS
-	SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): already run, returning" << endl;
+        SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): already run, returning" << endl;
 #endif
-	return;
+        return;
     }
 
 #ifdef DEBUG_DSSI_PROCESS
@@ -1192,81 +1192,81 @@
 
     size_t index = 0;
     unsigned long *counts = (unsigned long *)
-	alloca(m_groupLocalEventBufferCount * sizeof(unsigned long));
+        alloca(m_groupLocalEventBufferCount * sizeof(unsigned long));
     LADSPA_Handle *instances = (LADSPA_Handle *)
-	alloca(m_groupLocalEventBufferCount * sizeof(LADSPA_Handle));
+        alloca(m_groupLocalEventBufferCount * sizeof(LADSPA_Handle));
 
     for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
 
-	if (index >= m_groupLocalEventBufferCount) break;
+        if (index >= m_groupLocalEventBufferCount) break;
 
-	DSSIPluginInstance *instance = *i;
-	counts[index] = 0;
-	instances[index] = instance->m_instanceHandle;
+        DSSIPluginInstance *instance = *i;
+        counts[index] = 0;
+        instances[index] = instance->m_instanceHandle;
 
 #ifdef DEBUG_DSSI_PROCESS
-	SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): running " << instance << endl;
+        SVDEBUG << "DSSIPluginInstance::runGrouped(" << blockTime << "): running " << instance << endl;
 #endif
 
-	if (instance->m_pending.program >= 0 &&
-	    instance->m_descriptor->select_program) {
-	    int program = instance->m_pending.program;
-	    int bank = instance->m_pending.lsb + 128 * instance->m_pending.msb;
-	    instance->m_pending.lsb = instance->m_pending.msb = instance->m_pending.program = -1;
-	    instance->m_descriptor->select_program
-		(instance->m_instanceHandle, bank, program);
-	}
+        if (instance->m_pending.program >= 0 &&
+            instance->m_descriptor->select_program) {
+            int program = instance->m_pending.program;
+            int bank = instance->m_pending.lsb + 128 * instance->m_pending.msb;
+            instance->m_pending.lsb = instance->m_pending.msb = instance->m_pending.program = -1;
+            instance->m_descriptor->select_program
+                (instance->m_instanceHandle, bank, program);
+        }
 
-	while (instance->m_eventBuffer.getReadSpace() > 0) {
+        while (instance->m_eventBuffer.getReadSpace() > 0) {
 
-	    snd_seq_event_t *ev = m_groupLocalEventBuffers[index] + counts[index];
-	    *ev = instance->m_eventBuffer.peekOne();
-	    bool accept = true;
+            snd_seq_event_t *ev = m_groupLocalEventBuffers[index] + counts[index];
+            *ev = instance->m_eventBuffer.peekOne();
+            bool accept = true;
 
-	    RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+            RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
 
-	    sv_frame_t frameOffset = 0;
-	    if (evTime > blockTime) {
-		frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
-	    }
+            sv_frame_t frameOffset = 0;
+            if (evTime > blockTime) {
+                frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+            }
 
 #ifdef DEBUG_DSSI_PROCESS
-	    SVDEBUG << "DSSIPluginInstance::runGrouped: evTime " << evTime << ", frameOffset " << frameOffset
-		      << ", block size " << m_blockSize << endl;
+            SVDEBUG << "DSSIPluginInstance::runGrouped: evTime " << evTime << ", frameOffset " << frameOffset
+                      << ", block size " << m_blockSize << endl;
 #endif
 
-	    if (frameOffset >= int(m_blockSize)) break;
-	    if (frameOffset < 0) frameOffset = 0;
+            if (frameOffset >= int(m_blockSize)) break;
+            if (frameOffset < 0) frameOffset = 0;
 
-	    ev->time.tick = snd_seq_tick_time_t(frameOffset);
-	    instance->m_eventBuffer.skip(1);
+            ev->time.tick = snd_seq_tick_time_t(frameOffset);
+            instance->m_eventBuffer.skip(1);
 
-	    if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
-		accept = instance->handleController(ev);
-	    } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
-		instance->m_pending.program = ev->data.control.value;
-		accept = false;
-	    }
+            if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+                accept = instance->handleController(ev);
+            } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+                instance->m_pending.program = ev->data.control.value;
+                accept = false;
+            }
 
-	    if (accept) {
-		if (++counts[index] >= EVENT_BUFFER_SIZE) break;
-	    }
-	}
+            if (accept) {
+                if (++counts[index] >= EVENT_BUFFER_SIZE) break;
+            }
+        }
 
-	++index;
+        ++index;
     }
 
     m_descriptor->run_multiple_synths(index,
-				      instances,
-				      m_blockSize,
-				      m_groupLocalEventBuffers,
-				      counts);
+                                      instances,
+                                      m_blockSize,
+                                      m_groupLocalEventBuffers,
+                                      counts);
 }
 
 int
 DSSIPluginInstance::requestMidiSend(LADSPA_Handle /* instance */,
-				    unsigned char /* ports */,
-				    unsigned char /* channels */)
+                                    unsigned char /* ports */,
+                                    unsigned char /* channels */)
 {
     // This is called from a non-RT context (during instantiate)
 
@@ -1276,8 +1276,8 @@
 
 void
 DSSIPluginInstance::midiSend(LADSPA_Handle /* instance */,
-			     snd_seq_event_t * /* events */,
-			     unsigned long /* eventCount */)
+                             snd_seq_event_t * /* events */,
+                             unsigned long /* eventCount */)
 {
     // This is likely to be called from an RT context
 
@@ -1288,14 +1288,14 @@
 DSSIPluginInstance::NonRTPluginThread::run()
 {
     while (!m_exiting) {
-	m_runFunction(m_handle);
-	usleep(100000);
+        m_runFunction(m_handle);
+        usleep(100000);
     }
 }
 
 int
 DSSIPluginInstance::requestNonRTThread(LADSPA_Handle instance,
-				       void (*runFunction)(LADSPA_Handle))
+                                       void (*runFunction)(LADSPA_Handle))
 {
     NonRTPluginThread *thread = new NonRTPluginThread(instance, runFunction);
     m_threads[instance].insert(thread);
@@ -1312,7 +1312,7 @@
     if (!m_descriptor || !m_descriptor->LADSPA_Plugin->deactivate) return;
 
     for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
-	m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+        m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
     }
 
     m_descriptor->LADSPA_Plugin->deactivate(m_instanceHandle);
@@ -1332,11 +1332,11 @@
     if (!m_descriptor) return;
 
     if (!m_descriptor->LADSPA_Plugin->cleanup) {
-	cerr << "Bad plugin: plugin id "
-		  << m_descriptor->LADSPA_Plugin->UniqueID
-		  << ":" << m_descriptor->LADSPA_Plugin->Label
-		  << " has no cleanup method!" << endl;
-	return;
+        cerr << "Bad plugin: plugin id "
+                  << m_descriptor->LADSPA_Plugin->UniqueID
+                  << ":" << m_descriptor->LADSPA_Plugin->Label
+                  << " has no cleanup method!" << endl;
+        return;
     }
 
     m_descriptor->LADSPA_Plugin->cleanup(m_instanceHandle);
--- a/plugin/DSSIPluginInstance.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/DSSIPluginInstance.h	Mon Sep 17 13:51:14 2018 +0100
@@ -68,7 +68,7 @@
 
     virtual std::string configure(std::string key, std::string value);
     virtual void sendEvent(const RealTime &eventTime,
-			   const void *event);
+                           const void *event);
     virtual void clearEvents();
 
     virtual int getBufferSize() const { return m_blockSize; }
@@ -107,13 +107,13 @@
     // Constructor that creates the buffers internally
     // 
     DSSIPluginInstance(RealTimePluginFactory *factory,
-		       int client,
-		       QString identifier,
-		       int position,
-		       sv_samplerate_t sampleRate,
-		       int blockSize,
-		       int idealChannelCount,
-		       const DSSI_Descriptor* descriptor);
+                       int client,
+                       QString identifier,
+                       int position,
+                       sv_samplerate_t sampleRate,
+                       int blockSize,
+                       int idealChannelCount,
+                       const DSSI_Descriptor* descriptor);
     
     void init();
     void instantiate(sv_samplerate_t sampleRate);
@@ -132,13 +132,13 @@
 
     // For use in DSSIPluginFactory (set in the DSSI_Host_Descriptor):
     static int requestMidiSend(LADSPA_Handle instance,
-			       unsigned char ports,
-			       unsigned char channels);
+                               unsigned char ports,
+                               unsigned char channels);
     static void midiSend(LADSPA_Handle instance,
-			 snd_seq_event_t *events,
-			 unsigned long eventCount);
+                         snd_seq_event_t *events,
+                         unsigned long eventCount);
     static int requestNonRTThread(LADSPA_Handle instance,
-				  void (*runFunction)(LADSPA_Handle));
+                                  void (*runFunction)(LADSPA_Handle));
 
     int                        m_client;
     int                        m_position;
@@ -156,16 +156,16 @@
     std::vector<int>          m_audioPortsOut;
 
     struct ProgramControl {
-	int msb;
-	int lsb;
-	int program;
+        int msb;
+        int lsb;
+        int program;
     };
     ProgramControl m_pending;
 
     struct ProgramDescriptor {
-	int bank;
-	int program;
-	std::string name;
+        int bank;
+        int program;
+        std::string name;
     };
     mutable std::vector<ProgramDescriptor> m_cachedPrograms;
     mutable bool m_programCacheValid;
@@ -203,19 +203,19 @@
     class NonRTPluginThread : public Thread
     {
     public:
-	NonRTPluginThread(LADSPA_Handle handle,
-			  void (*runFunction)(LADSPA_Handle)) :
-	    m_handle(handle),
-	    m_runFunction(runFunction),
-	    m_exiting(false) { }
+        NonRTPluginThread(LADSPA_Handle handle,
+                          void (*runFunction)(LADSPA_Handle)) :
+            m_handle(handle),
+            m_runFunction(runFunction),
+            m_exiting(false) { }
 
-	virtual void run();
-	void setExiting() { m_exiting = true; }
+        virtual void run();
+        void setExiting() { m_exiting = true; }
 
     protected:
-	LADSPA_Handle m_handle;
-	void (*m_runFunction)(LADSPA_Handle);
-	bool m_exiting;
+        LADSPA_Handle m_handle;
+        void (*m_runFunction)(LADSPA_Handle);
+        bool m_exiting;
     };
     static std::map<LADSPA_Handle, std::set<NonRTPluginThread *> > m_threads;
 };
--- a/plugin/FeatureExtractionPluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/FeatureExtractionPluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -34,17 +34,13 @@
     /**
      * Return all installed plugin identifiers.
      */
-    virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage) {
-        return instance()->getPluginIdentifiers(errorMessage);
-    }
-
+    virtual std::vector<QString> getPluginIdentifiers(QString &errorMsg) = 0;
+    
     /**
      * Return static data for the given plugin.
      */
-    virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier) {
-        return instance()->getPluginStaticData(identifier);
-    }
-
+    virtual piper_vamp::PluginStaticData getPluginStaticData(QString ident) = 0;
+    
     /**
      * Instantiate (load) and return pointer to the plugin with the
      * given identifier, at the given sample rate. We don't set
@@ -52,16 +48,20 @@
      * via initialize() on the plugin itself after loading.
      */
     virtual Vamp::Plugin *instantiatePlugin(QString identifier,
-                                            sv_samplerate_t inputSampleRate) {
-        return instance()->instantiatePlugin(identifier, inputSampleRate);
-    }
-
+                                            sv_samplerate_t inputSampleRate) = 0;
+    
     /**
      * Get category metadata about a plugin (without instantiating it).
      */
-    virtual QString getPluginCategory(QString identifier) {
-        return instance()->getPluginCategory(identifier);
-    }
+    virtual QString getPluginCategory(QString identifier) = 0;
+
+    /**
+     * Get the full file path (including both directory and filename)
+     * of the library file that provides a given plugin
+     * identifier. Note getPluginIdentifiers() must have been called
+     * before this has access to the necessary information.
+     */
+    virtual QString getPluginLibraryPath(QString identifier) = 0;
 };
 
 #endif
--- a/plugin/LADSPAPluginFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/LADSPAPluginFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -40,6 +40,7 @@
 #include "lrdf.h"
 #endif // HAVE_LRDF
 
+using std::string;
 
 LADSPAPluginFactory::LADSPAPluginFactory()
 {
@@ -51,9 +52,9 @@
 LADSPAPluginFactory::~LADSPAPluginFactory()
 {
     for (std::set<RealTimePluginInstance *>::iterator i = m_instances.begin();
-	 i != m_instances.end(); ++i) {
-	(*i)->setFactory(0);
-	delete *i;
+         i != m_instances.end(); ++i) {
+        (*i)->setFactory(0);
+        delete *i;
     }
     m_instances.clear();
     unloadUnusedLibraries();
@@ -69,68 +70,74 @@
     return m_identifiers;
 }
 
+QString
+LADSPAPluginFactory::getPluginLibraryPath(QString identifier)
+{
+    return m_libraries[identifier];
+}
+
 void
 LADSPAPluginFactory::enumeratePlugins(std::vector<QString> &list)
 {
     Profiler profiler("LADSPAPluginFactory::enumeratePlugins");
 
     for (std::vector<QString>::iterator i = m_identifiers.begin();
-	 i != m_identifiers.end(); ++i) {
+         i != m_identifiers.end(); ++i) {
 
-	const LADSPA_Descriptor *descriptor = getLADSPADescriptor(*i);
+        const LADSPA_Descriptor *descriptor = getLADSPADescriptor(*i);
 
-	if (!descriptor) {
-	    cerr << "WARNING: LADSPAPluginFactory::enumeratePlugins: couldn't get descriptor for identifier " << *i << endl;
-	    continue;
-	}
-	
-	list.push_back(*i);
-	list.push_back(descriptor->Name);
-	list.push_back(QString("%1").arg(descriptor->UniqueID));
-	list.push_back(descriptor->Label);
-	list.push_back(descriptor->Maker);
-	list.push_back(descriptor->Copyright);
-	list.push_back("false"); // is synth
-	list.push_back("false"); // is grouped
+        if (!descriptor) {
+            cerr << "WARNING: LADSPAPluginFactory::enumeratePlugins: couldn't get descriptor for identifier " << *i << endl;
+            continue;
+        }
+        
+        list.push_back(*i);
+        list.push_back(descriptor->Name);
+        list.push_back(QString("%1").arg(descriptor->UniqueID));
+        list.push_back(descriptor->Label);
+        list.push_back(descriptor->Maker);
+        list.push_back(descriptor->Copyright);
+        list.push_back("false"); // is synth
+        list.push_back("false"); // is grouped
 
-	if (m_taxonomy.find(*i) != m_taxonomy.end() && m_taxonomy[*i] != "") {
-//		cerr << "LADSPAPluginFactory: cat for " << *i << " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << endl;
-	    list.push_back(m_taxonomy[*i]);
-	} else {
-	    list.push_back("");
-//		cerr << "LADSPAPluginFactory: cat for " << *i << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << endl;
-	    
-	}
+        if (m_taxonomy.find(*i) != m_taxonomy.end() && m_taxonomy[*i] != "") {
+//                cerr << "LADSPAPluginFactory: cat for " << *i << " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << endl;
+            list.push_back(m_taxonomy[*i]);
+        } else {
+            list.push_back("");
+//                cerr << "LADSPAPluginFactory: cat for " << *i << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << endl;
+            
+        }
 
-	list.push_back(QString("%1").arg(descriptor->PortCount));
+        list.push_back(QString("%1").arg(descriptor->PortCount));
 
-	for (int p = 0; p < (int)descriptor->PortCount; ++p) {
+        for (int p = 0; p < (int)descriptor->PortCount; ++p) {
 
-	    int type = 0;
-	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
-		type |= PortType::Control;
-	    } else {
-		type |= PortType::Audio;
-	    }
-	    if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
-		type |= PortType::Input;
-	    } else {
-		type |= PortType::Output;
-	    }
+            int type = 0;
+            if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+                type |= PortType::Control;
+            } else {
+                type |= PortType::Audio;
+            }
+            if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+                type |= PortType::Input;
+            } else {
+                type |= PortType::Output;
+            }
 
-	    list.push_back(QString("%1").arg(p));
-	    list.push_back(descriptor->PortNames[p]);
-	    list.push_back(QString("%1").arg(type));
-	    list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
-	    list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
-	}
+            list.push_back(QString("%1").arg(p));
+            list.push_back(descriptor->PortNames[p]);
+            list.push_back(QString("%1").arg(type));
+            list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+            list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+        }
     }
 
     unloadUnusedLibraries();
 }
-	
+        
 const RealTimePluginDescriptor *
 LADSPAPluginFactory::getPluginDescriptor(QString identifier) const
 {
@@ -148,20 +155,20 @@
 LADSPAPluginFactory::getPortMinimum(const LADSPA_Descriptor *descriptor, int port)
 {
     LADSPA_PortRangeHintDescriptor d =
-	descriptor->PortRangeHints[port].HintDescriptor;
+        descriptor->PortRangeHints[port].HintDescriptor;
 
     float minimum = 0.f;
-		
+                
     if (LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
-	float lb = descriptor->PortRangeHints[port].LowerBound;
-	minimum = lb;
+        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.f, ub - 1.f);
+        float ub = descriptor->PortRangeHints[port].UpperBound;
+        minimum = std::min(0.f, ub - 1.f);
     }
     
     if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
-	minimum = float(minimum * m_sampleRate);
+        minimum = float(minimum * m_sampleRate);
     }
 
     if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
@@ -175,20 +182,20 @@
 LADSPAPluginFactory::getPortMaximum(const LADSPA_Descriptor *descriptor, int port)
 {
     LADSPA_PortRangeHintDescriptor d =
-	descriptor->PortRangeHints[port].HintDescriptor;
+        descriptor->PortRangeHints[port].HintDescriptor;
 
     float maximum = 1.f;
     
     if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
-	float ub = descriptor->PortRangeHints[port].UpperBound;
-	maximum = ub;
+        float ub = descriptor->PortRangeHints[port].UpperBound;
+        maximum = ub;
     } else {
-	float lb = descriptor->PortRangeHints[port].LowerBound;
-	maximum = lb + 1.f;
+        float lb = descriptor->PortRangeHints[port].LowerBound;
+        maximum = lb + 1.f;
     }
     
     if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
-	maximum = float(maximum * m_sampleRate);
+        maximum = float(maximum * m_sampleRate);
     }
 
     return maximum;
@@ -202,19 +209,19 @@
     float deft;
 
     if (m_portDefaults.find(descriptor->UniqueID) != 
-	m_portDefaults.end()) {
-	if (m_portDefaults[descriptor->UniqueID].find(port) !=
-	    m_portDefaults[descriptor->UniqueID].end()) {
+        m_portDefaults.end()) {
+        if (m_portDefaults[descriptor->UniqueID].find(port) !=
+            m_portDefaults[descriptor->UniqueID].end()) {
 
-	    deft = m_portDefaults[descriptor->UniqueID][port];
-	    if (deft < minimum) deft = minimum;
-	    if (deft > maximum) deft = maximum;
-	    return deft;
-	}
+            deft = m_portDefaults[descriptor->UniqueID][port];
+            if (deft < minimum) deft = minimum;
+            if (deft > maximum) deft = maximum;
+            return deft;
+        }
     }
 
     LADSPA_PortRangeHintDescriptor d =
-	descriptor->PortRangeHints[port].HintDescriptor;
+        descriptor->PortRangeHints[port].HintDescriptor;
 
     bool logarithmic = LADSPA_IS_HINT_LOGARITHMIC(d);
     
@@ -230,61 +237,61 @@
 //    SVDEBUG << "LADSPAPluginFactory::getPortDefault: hint = " << d << endl;
 
     if (!LADSPA_IS_HINT_HAS_DEFAULT(d)) {
-	
-	deft = minimum;
-	
+        
+        deft = minimum;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_MINIMUM(d)) {
-	
-	deft = minimum;
-	
+        
+        deft = minimum;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_LOW(d)) {
-	
-	if (logarithmic) {
-	    deft = powf(10, logmin * 0.75f + logmax * 0.25f);
-	} else {
-	    deft = minimum * 0.75f + maximum * 0.25f;
-	}
-	
+        
+        if (logarithmic) {
+            deft = powf(10, logmin * 0.75f + logmax * 0.25f);
+        } else {
+            deft = minimum * 0.75f + maximum * 0.25f;
+        }
+        
     } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(d)) {
-	
-	if (logarithmic) {
-	    deft = powf(10, logmin * 0.5f + logmax * 0.5f);
-	} else {
-	    deft = minimum * 0.5f + maximum * 0.5f;
-	}
-	
+        
+        if (logarithmic) {
+            deft = powf(10, logmin * 0.5f + logmax * 0.5f);
+        } else {
+            deft = minimum * 0.5f + maximum * 0.5f;
+        }
+        
     } else if (LADSPA_IS_HINT_DEFAULT_HIGH(d)) {
-	
-	if (logarithmic) {
-	    deft = powf(10, logmin * 0.25f + logmax * 0.75f);
-	} else {
-	    deft = minimum * 0.25f + maximum * 0.75f;
-	}
-	
+        
+        if (logarithmic) {
+            deft = powf(10, logmin * 0.25f + logmax * 0.75f);
+        } else {
+            deft = minimum * 0.25f + maximum * 0.75f;
+        }
+        
     } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(d)) {
-	
-	deft = maximum;
-	
+        
+        deft = maximum;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_0(d)) {
-	
-	deft = 0.0;
-	
+        
+        deft = 0.0;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_1(d)) {
-	
-	deft = 1.0;
-	
+        
+        deft = 1.0;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_100(d)) {
-	
-	deft = 100.0;
-	
+        
+        deft = 100.0;
+        
     } else if (LADSPA_IS_HINT_DEFAULT_440(d)) {
-	
-//	deft = 440.0;
+        
+//        deft = 440.0;
         deft = (float)Preferences::getInstance()->getTuningFrequency();
-	
+        
     } else {
-	
-	deft = minimum;
+        
+        deft = minimum;
     }
 
 //!!! No -- the min and max have already been multiplied by the rate,
@@ -292,7 +299,7 @@
 //doesn't want to be multiplied by the rate either
     
 //    if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
-//	deft *= m_sampleRate;
+//        deft *= m_sampleRate;
 //    }
 
     return deft;
@@ -316,7 +323,7 @@
 LADSPAPluginFactory::getPortDisplayHint(const LADSPA_Descriptor *descriptor, int port)
 {
     LADSPA_PortRangeHintDescriptor d =
-	descriptor->PortRangeHints[port].HintDescriptor;
+        descriptor->PortRangeHints[port].HintDescriptor;
     int hint = PortHint::NoHint;
 
     if (LADSPA_IS_HINT_TOGGLED(d)) hint |= PortHint::Toggled;
@@ -329,11 +336,11 @@
 
 RealTimePluginInstance *
 LADSPAPluginFactory::instantiatePlugin(QString identifier,
-				       int instrument,
-				       int position,
-				       sv_samplerate_t sampleRate,
-				       int blockSize,
-				       int channels)
+                                       int instrument,
+                                       int position,
+                                       sv_samplerate_t sampleRate,
+                                       int blockSize,
+                                       int channels)
 {
     Profiler profiler("LADSPAPluginFactory::instantiatePlugin");
 
@@ -341,19 +348,19 @@
 
     if (descriptor) {
 
-	LADSPAPluginInstance *instance =
-	    new LADSPAPluginInstance
-	    (this, instrument, identifier, position, sampleRate, blockSize, channels,
-	     descriptor);
+        LADSPAPluginInstance *instance =
+            new LADSPAPluginInstance
+            (this, instrument, identifier, position, sampleRate, blockSize, channels,
+             descriptor);
 
-	m_instances.insert(instance);
+        m_instances.insert(instance);
 
 #ifdef DEBUG_LADSPA_PLUGIN_FACTORY
         SVDEBUG << "LADSPAPluginFactory::instantiatePlugin("
                   << identifier << ": now have " << m_instances.size() << " instances" << endl;
 #endif
 
-	return instance;
+        return instance;
     }
 
     return 0;
@@ -361,14 +368,14 @@
 
 void
 LADSPAPluginFactory::releasePlugin(RealTimePluginInstance *instance,
-				   QString identifier)
+                                   QString identifier)
 {
     Profiler profiler("LADSPAPluginFactory::releasePlugin");
 
     if (m_instances.find(instance) == m_instances.end()) {
-	cerr << "WARNING: LADSPAPluginFactory::releasePlugin: Not one of mine!"
-		  << endl;
-	return;
+        cerr << "WARNING: LADSPAPluginFactory::releasePlugin: Not one of mine!"
+                  << endl;
+        return;
     }
 
     QString type, soname, label;
@@ -379,16 +386,16 @@
     bool stillInUse = false;
 
     for (std::set<RealTimePluginInstance *>::iterator ii = m_instances.begin();
-	 ii != m_instances.end(); ++ii) {
-	QString itype, isoname, ilabel;
-	PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel);
-	if (isoname == soname) {
+         ii != m_instances.end(); ++ii) {
+        QString itype, isoname, ilabel;
+        PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel);
+        if (isoname == soname) {
 #ifdef DEBUG_LADSPA_PLUGIN_FACTORY
-	    SVDEBUG << "LADSPAPluginFactory::releasePlugin: dll " << soname << " is still in use for plugin " << ilabel << endl;
+            SVDEBUG << "LADSPAPluginFactory::releasePlugin: dll " << soname << " is still in use for plugin " << ilabel << endl;
 #endif
-	    stillInUse = true;
-	    break;
-	}
+            stillInUse = true;
+            break;
+        }
     }
     
     if (!stillInUse) {
@@ -413,32 +420,32 @@
     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
 
     if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
-	loadLibrary(soname);
-	if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
-	    cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: loadLibrary failed for " << soname << endl;
-	    return 0;
-	}
+        loadLibrary(soname);
+        if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+            SVCERR << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: loadLibrary failed for " << soname << endl;
+            return 0;
+        }
     }
 
     void *libraryHandle = m_libraryHandles[soname];
 
     LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
-	DLSYM(libraryHandle, "ladspa_descriptor");
+        DLSYM(libraryHandle, "ladspa_descriptor");
 
     if (!fn) {
-	cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No descriptor function in library " << soname << endl;
-	return 0;
+        SVCERR << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No descriptor function in library " << soname << endl;
+        return 0;
     }
 
     const LADSPA_Descriptor *descriptor = 0;
     
     int index = 0;
     while ((descriptor = fn(index))) {
-	if (descriptor->Label == label) return descriptor;
-	++index;
+        if (descriptor->Label == label) return descriptor;
+        ++index;
     }
 
-    cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No such plugin as " << label << " in library " << soname << endl;
+    SVCERR << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No such plugin as " << label << " in library " << soname << endl;
 
     return 0;
 }
@@ -455,7 +462,7 @@
 
     if (QFileInfo(soName).exists()) {
         DLERROR();
-        cerr << "LADSPAPluginFactory::loadLibrary: Library \"" << soName << "\" exists, but failed to load it" << endl;
+        SVCERR << "LADSPAPluginFactory::loadLibrary: Library \"" << soName << "\" exists, but failed to load it" << endl;
         return;
     }
 
@@ -465,7 +472,7 @@
     QString base = QFileInfo(soName).baseName();
 
     for (std::vector<QString>::iterator i = pathList.begin();
-	 i != pathList.end(); ++i) {
+         i != pathList.end(); ++i) {
         
 #ifdef DEBUG_LADSPA_PLUGIN_FACTORY
         SVDEBUG << "Looking at: " << (*i) << endl;
@@ -477,7 +484,7 @@
 
         if (QFileInfo(dir.filePath(fileName)).exists()) {
 #ifdef DEBUG_LADSPA_PLUGIN_FACTORY
-            cerr << "Loading: " << fileName << endl;
+            SVDEBUG << "Loading: " << fileName << endl;
 #endif
             libraryHandle = DLOPEN(dir.filePath(fileName), RTLD_NOW);
             if (libraryHandle) {
@@ -486,11 +493,11 @@
             }
         }
 
-	for (unsigned int j = 0; j < dir.count(); ++j) {
+        for (unsigned int j = 0; j < dir.count(); ++j) {
             QString file = dir.filePath(dir[j]);
             if (QFileInfo(file).baseName() == base) {
 #ifdef DEBUG_LADSPA_PLUGIN_FACTORY
-                cerr << "Loading: " << file << endl;
+                SVDEBUG << "Loading: " << file << endl;
 #endif
                 libraryHandle = DLOPEN(file, RTLD_NOW);
                 if (libraryHandle) {
@@ -501,7 +508,7 @@
         }
     }
 
-    cerr << "LADSPAPluginFactory::loadLibrary: Failed to locate plugin library \"" << soName << "\"" << endl;
+    SVCERR << "LADSPAPluginFactory::loadLibrary: Failed to locate plugin library \"" << soName << "\"" << endl;
 }
 
 void
@@ -509,9 +516,9 @@
 {
     LibraryHandleMap::iterator li = m_libraryHandles.find(soName);
     if (li != m_libraryHandles.end()) {
-//	SVDEBUG << "unloading " << soname << endl;
-	DLCLOSE(m_libraryHandles[soName]);
-	m_libraryHandles.erase(li);
+//        SVDEBUG << "unloading " << soname << endl;
+        DLCLOSE(m_libraryHandles[soName]);
+        m_libraryHandles.erase(li);
     }
 }
 
@@ -521,26 +528,26 @@
     std::vector<QString> toUnload;
 
     for (LibraryHandleMap::iterator i = m_libraryHandles.begin();
-	 i != m_libraryHandles.end(); ++i) {
+         i != m_libraryHandles.end(); ++i) {
 
-	bool stillInUse = false;
+        bool stillInUse = false;
 
-	for (std::set<RealTimePluginInstance *>::iterator ii = m_instances.begin();
-	     ii != m_instances.end(); ++ii) {
+        for (std::set<RealTimePluginInstance *>::iterator ii = m_instances.begin();
+             ii != m_instances.end(); ++ii) {
 
-	    QString itype, isoname, ilabel;
-	    PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel);
-	    if (isoname == i->first) {
-		stillInUse = true;
-		break;
-	    }
-	}
+            QString itype, isoname, ilabel;
+            PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel);
+            if (isoname == i->first) {
+                stillInUse = true;
+                break;
+            }
+        }
 
-	if (!stillInUse) toUnload.push_back(i->first);
+        if (!stillInUse) toUnload.push_back(i->first);
     }
 
     for (std::vector<QString>::iterator i = toUnload.begin();
-	 i != toUnload.end(); ++i) {
+         i != toUnload.end(); ++i) {
         if (*i != PluginIdentifier::BUILTIN_PLUGIN_SONAME) {
             unloadLibrary(*i);
         }
@@ -560,42 +567,42 @@
 LADSPAPluginFactory::getPluginPath()
 {
     std::vector<QString> pathList;
-    std::string path;
+    string path;
 
-    char *cpath = getenv("LADSPA_PATH");
-    if (cpath) path = cpath;
+    (void)getEnvUtf8("LADSPA_PATH", path);
 
     if (path == "") {
 
         path = DEFAULT_LADSPA_PATH;
 
-	char *home = getenv("HOME");
-	if (home) {
-            std::string::size_type f;
-            while ((f = path.find("$HOME")) != std::string::npos &&
+        string home;
+        if (getEnvUtf8("HOME", home)) {
+            string::size_type f;
+            while ((f = path.find("$HOME")) != string::npos &&
                    f < path.length()) {
                 path.replace(f, 5, home);
             }
         }
 
 #ifdef _WIN32
-        const char *pfiles = getenv("ProgramFiles");
-        if (!pfiles) pfiles = "C:\\Program Files";
-        {
-        std::string::size_type f;
-        while ((f = path.find("%ProgramFiles%")) != std::string::npos &&
+        string pfiles;
+        if (!getEnvUtf8("ProgramFiles", pfiles)) {
+            pfiles = "C:\\Program Files";
+        }
+
+        string::size_type f;
+        while ((f = path.find("%ProgramFiles%")) != string::npos &&
                f < path.length()) {
             path.replace(f, 14, pfiles);
         }
-        }
 #endif
     }
 
-    std::string::size_type index = 0, newindex = 0;
+    string::size_type index = 0, newindex = 0;
 
     while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) {
-	pathList.push_back(path.substr(index, newindex - index).c_str());
-	index = newindex + 1;
+        pathList.push_back(path.substr(index, newindex - index).c_str());
+        index = newindex + 1;
     }
     
     pathList.push_back(path.substr(index).c_str());
@@ -616,8 +623,8 @@
     lrdfPaths.push_back("/usr/share/ladspa/rdf");
 
     for (std::vector<QString>::iterator i = pathList.begin();
-	 i != pathList.end(); ++i) {
-	lrdfPaths.push_back(*i + "/rdf");
+         i != pathList.end(); ++i) {
+        lrdfPaths.push_back(*i + "/rdf");
     }
 
     baseUri = LADSPA_BASE;
@@ -636,10 +643,10 @@
     std::vector<QString> pathList = getPluginPath();
 
 //    SVDEBUG << "LADSPAPluginFactory::discoverPlugins - "
-//	      << "discovering plugins; path is ";
+//              << "discovering plugins; path is ";
 //    for (std::vector<QString>::iterator i = pathList.begin();
-//	 i != pathList.end(); ++i) {
-//	SVDEBUG << "[" << i-<< "] ";
+//         i != pathList.end(); ++i) {
+//        SVDEBUG << "[" << i-<< "] ";
 //    }
 //    SVDEBUG << endl;
 
@@ -652,17 +659,17 @@
     bool haveSomething = false;
 
     for (size_t i = 0; i < lrdfPaths.size(); ++i) {
-	QDir dir(lrdfPaths[i], "*.rdf;*.rdfs");
-	for (unsigned int j = 0; j < dir.count(); ++j) {
-	    if (!lrdf_read_file(QString("file:" + lrdfPaths[i] + "/" + dir[j]).toStdString().c_str())) {
-//		cerr << "LADSPAPluginFactory: read RDF file " << (lrdfPaths[i] + "/" + dir[j]) << endl;
-		haveSomething = true;
-	    }
-	}
+        QDir dir(lrdfPaths[i], "*.rdf;*.rdfs");
+        for (unsigned int j = 0; j < dir.count(); ++j) {
+            if (!lrdf_read_file(QString("file:" + lrdfPaths[i] + "/" + dir[j]).toStdString().c_str())) {
+//                cerr << "LADSPAPluginFactory: read RDF file " << (lrdfPaths[i] + "/" + dir[j]) << endl;
+                haveSomething = true;
+            }
+        }
     }
 
     if (haveSomething) {
-	generateTaxonomy(baseUri + "Plugin", "");
+        generateTaxonomy(baseUri + "Plugin", "");
     }
 #endif // HAVE_LRDF
 
@@ -688,11 +695,11 @@
     }
 
     LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
-	DLSYM(libraryHandle, "ladspa_descriptor");
+        DLSYM(libraryHandle, "ladspa_descriptor");
 
     if (!fn) {
-	cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: No descriptor function in " << soname << endl;
-	return;
+        cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: No descriptor function in " << soname << endl;
+        return;
     }
 
     const LADSPA_Descriptor *descriptor = 0;
@@ -712,67 +719,67 @@
         rtd->audioOutputPortCount = 0;
         rtd->controlOutputPortCount = 0;
 
-	QString identifier = PluginIdentifier::createIdentifier
-	    ("ladspa", soname, descriptor->Label);
+        QString identifier = PluginIdentifier::createIdentifier
+            ("ladspa", soname, descriptor->Label);
 
 #ifdef HAVE_LRDF
-	char *def_uri = 0;
-	lrdf_defaults *defs = 0;
-		
+        char *def_uri = 0;
+        lrdf_defaults *defs = 0;
+                
         if (m_lrdfTaxonomy[descriptor->UniqueID] != "") {
             m_taxonomy[identifier] = m_lrdfTaxonomy[descriptor->UniqueID];
 //            cerr << "set id \"" << identifier << "\" to cat \"" << m_taxonomy[identifier] << "\" from LRDF" << endl;
 //            cout << identifier << "::" << m_taxonomy[identifier] << endl;
         }
 
-	QString category = m_taxonomy[identifier];
-	
-	if (category == "") {
-	    std::string name = rtd->name;
-	    if (name.length() > 4 &&
-		name.substr(name.length() - 4) == " VST") {
-		category = "VST effects";
-		m_taxonomy[identifier] = category;
-	    }
-	}
-	
+        QString category = m_taxonomy[identifier];
+        
+        if (category == "") {
+            string name = rtd->name;
+            if (name.length() > 4 &&
+                name.substr(name.length() - 4) == " VST") {
+                category = "VST effects";
+                m_taxonomy[identifier] = category;
+            }
+        }
+        
         rtd->category = category.toStdString();
 
-//	cerr << "Plugin id is " << descriptor->UniqueID
-//		  << ", category is \"" << (category ? category : QString("(none)"))
-//		  << "\", name is " << descriptor->Name
-//		  << ", label is " << descriptor->Label
-//		  << endl;
-	
-	def_uri = lrdf_get_default_uri(descriptor->UniqueID);
-	if (def_uri) {
-	    defs = lrdf_get_setting_values(def_uri);
-	}
+//        cerr << "Plugin id is " << descriptor->UniqueID
+//                  << ", category is \"" << (category ? category : QString("(none)"))
+//                  << "\", name is " << descriptor->Name
+//                  << ", label is " << descriptor->Label
+//                  << endl;
+        
+        def_uri = lrdf_get_default_uri(descriptor->UniqueID);
+        if (def_uri) {
+            defs = lrdf_get_setting_values(def_uri);
+        }
 
-	unsigned int controlPortNumber = 1;
-	
-	for (int i = 0; i < (int)descriptor->PortCount; i++) {
-	    
-	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
-		
-		if (def_uri && defs) {
-		    
-		    for (unsigned int j = 0; j < 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 " << descriptor->PortNames[i] << endl;
-			    m_portDefaults[descriptor->UniqueID][i] =
-				defs->items[j].value;
-			}
-		    }
-		}
-		
-		++controlPortNumber;
-	    }
-	}
+        unsigned int controlPortNumber = 1;
+        
+        for (int i = 0; i < (int)descriptor->PortCount; i++) {
+            
+            if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
+                
+                if (def_uri && defs) {
+                    
+                    for (unsigned int j = 0; j < 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 " << descriptor->PortNames[i] << endl;
+                            m_portDefaults[descriptor->UniqueID][i] =
+                                defs->items[j].value;
+                        }
+                    }
+                }
+                
+                ++controlPortNumber;
+            }
+        }
 #endif // HAVE_LRDF
 
-	for (int i = 0; i < (int)descriptor->PortCount; i++) {
-	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[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;
                 } else {
@@ -792,11 +799,13 @@
             }
         }
 
-	m_identifiers.push_back(identifier);
+        m_identifiers.push_back(identifier);
+
+        m_libraries[identifier] = soname;
 
         m_rtDescriptors[identifier] = rtd;
 
-	++index;
+        ++index;
     }
 
     if (DLCLOSE(libraryHandle) != 0) {
@@ -812,44 +821,44 @@
     std::vector<QString> path;
 
     for (size_t i = 0; i < pluginPath.size(); ++i) {
-	if (pluginPath[i].contains("/lib/")) {
-	    QString p(pluginPath[i]);
+        if (pluginPath[i].contains("/lib/")) {
+            QString p(pluginPath[i]);
             path.push_back(p);
-	    p.replace("/lib/", "/share/");
-	    path.push_back(p);
-//	    SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: path element " << p << endl;
-	}
-	path.push_back(pluginPath[i]);
-//	SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: path element " << pluginPath[i] << endl;
+            p.replace("/lib/", "/share/");
+            path.push_back(p);
+//            SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: path element " << p << endl;
+        }
+        path.push_back(pluginPath[i]);
+//        SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: path element " << pluginPath[i] << endl;
     }
 
     for (size_t i = 0; i < path.size(); ++i) {
 
-	QDir dir(path[i], "*.cat");
+        QDir dir(path[i], "*.cat");
 
-//	SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
-	for (unsigned int j = 0; j < dir.count(); ++j) {
+//        SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
+        for (unsigned int j = 0; j < dir.count(); ++j) {
 
-	    QFile file(path[i] + "/" + dir[j]);
+            QFile file(path[i] + "/" + dir[j]);
 
-//	    SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
+//            SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
 
-	    if (file.open(QIODevice::ReadOnly)) {
-//		    cerr << "...opened" << endl;
-		QTextStream stream(&file);
-		QString line;
+            if (file.open(QIODevice::ReadOnly)) {
+//                    cerr << "...opened" << endl;
+                QTextStream stream(&file);
+                QString line;
 
-		while (!stream.atEnd()) {
-		    line = stream.readLine();
-//		    cerr << "line is: \"" << line << "\"" << endl;
-		    QString id = PluginIdentifier::canonicalise
+                while (!stream.atEnd()) {
+                    line = stream.readLine();
+//                    cerr << "line is: \"" << line << "\"" << endl;
+                    QString id = PluginIdentifier::canonicalise
                         (line.section("::", 0, 0));
-		    QString cat = line.section("::", 1, 1);
-		    m_taxonomy[id] = cat;
-//		    cerr << "set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
-		}
-	    }
-	}
+                    QString cat = line.section("::", 1, 1);
+                    m_taxonomy[id] = cat;
+//                    cerr << "set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
+                }
+            }
+        }
     }
 }    
 
@@ -860,21 +869,21 @@
     lrdf_uris *uris = lrdf_get_instances(uri.toStdString().c_str());
 
     if (uris != NULL) {
-	for (unsigned int i = 0; i < uris->count; ++i) {
-	    m_lrdfTaxonomy[lrdf_get_uid(uris->items[i])] = base;
-	}
-	lrdf_free_uris(uris);
+        for (unsigned int i = 0; i < uris->count; ++i) {
+            m_lrdfTaxonomy[lrdf_get_uid(uris->items[i])] = base;
+        }
+        lrdf_free_uris(uris);
     }
 
     uris = lrdf_get_subclasses(uri.toStdString().c_str());
 
     if (uris != NULL) {
-	for (unsigned int i = 0; i < uris->count; ++i) {
-	    char *label = lrdf_get_label(uris->items[i]);
-	    generateTaxonomy(uris->items[i],
-			     base + (base.length() > 0 ? " > " : "") + label);
-	}
-	lrdf_free_uris(uris);
+        for (unsigned int i = 0; i < uris->count; ++i) {
+            char *label = lrdf_get_label(uris->items[i]);
+            generateTaxonomy(uris->items[i],
+                             base + (base.length() > 0 ? " > " : "") + label);
+        }
+        lrdf_free_uris(uris);
     }
 #else
     // avoid unused parameter
--- a/plugin/LADSPAPluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/LADSPAPluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -47,20 +47,24 @@
     virtual const RealTimePluginDescriptor *getPluginDescriptor(QString identifier) const;
 
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
-						      int clientId,
-						      int position,
-						      sv_samplerate_t sampleRate,
-						      int blockSize,
-						      int channels);
+                                                      int clientId,
+                                                      int position,
+                                                      sv_samplerate_t sampleRate,
+                                                      int blockSize,
+                                                      int channels);
 
     virtual QString getPluginCategory(QString identifier);
 
+    virtual QString getPluginLibraryPath(QString identifier);
+    
     float getPortMinimum(const LADSPA_Descriptor *, int port);
     float getPortMaximum(const LADSPA_Descriptor *, int port);
     float getPortDefault(const LADSPA_Descriptor *, int port);
     float getPortQuantization(const LADSPA_Descriptor *, int port);
     int getPortDisplayHint(const LADSPA_Descriptor *, int port);
 
+    static std::vector<QString> getPluginPath();
+
 protected:
     LADSPAPluginFactory();
     friend class RealTimePluginFactory;
@@ -69,8 +73,6 @@
         return PluginScan::LADSPAPlugin;
     }
 
-    virtual std::vector<QString> getPluginPath();
-
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
 
     virtual void discoverPluginsFrom(QString soName);
@@ -86,6 +88,7 @@
     void unloadUnusedLibraries();
 
     std::vector<QString> m_identifiers;
+    std::map<QString, QString> m_libraries; // identifier -> full file path
     std::map<QString, RealTimePluginDescriptor *> m_rtDescriptors;
 
     std::map<QString, QString> m_taxonomy;
--- a/plugin/LADSPAPluginInstance.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/LADSPAPluginInstance.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -34,12 +34,12 @@
 
 
 LADSPAPluginInstance::LADSPAPluginInstance(RealTimePluginFactory *factory,
-					   int clientId,
-					   QString identifier,
+                                           int clientId,
+                                           QString identifier,
                                            int position,
-					   sv_samplerate_t sampleRate,
-					   int blockSize,
-					   int idealChannelCount,
+                                           sv_samplerate_t sampleRate,
+                                           int blockSize,
+                                           int idealChannelCount,
                                            const LADSPA_Descriptor* descriptor) :
     RealTimePluginInstance(factory, identifier),
     m_client(clientId),
@@ -67,18 +67,18 @@
     }
 
     for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) {
-	m_inputBuffers[i] = new sample_t[blockSize];
+        m_inputBuffers[i] = new sample_t[blockSize];
     }
     for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) {
-	m_outputBuffers[i] = new sample_t[blockSize];
+        m_outputBuffers[i] = new sample_t[blockSize];
     }
 
     m_ownBuffers = true;
 
     instantiate(sampleRate);
     if (isOK()) {
-	connectPorts();
-	activate();
+        connectPorts();
+        activate();
     }
 }
 
@@ -222,7 +222,7 @@
 {
 #ifdef DEBUG_LADSPA
     SVDEBUG << "LADSPAPluginInstance::init(" << idealChannelCount << "): plugin has "
-	      << m_descriptor->PortCount << " ports" << endl;
+              << m_descriptor->PortCount << " ports" << endl;
 #endif
 
     // Discover ports numbers and identities
@@ -233,44 +233,44 @@
 
             if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
 #ifdef DEBUG_LADSPA
-		SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is audio in" << endl;
+                SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is audio in" << endl;
 #endif
                 m_audioPortsIn.push_back(i);
-	    } else {
+            } else {
 #ifdef DEBUG_LADSPA
-		SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is audio out" << endl;
+                SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is audio out" << endl;
 #endif
                 m_audioPortsOut.push_back(i);
-	    }
+            }
 
         } else if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[i])) {
 
-	    if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
+            if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
 
 #ifdef DEBUG_LADSPA
-		SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is control in" << endl;
+                SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is control in" << endl;
 #endif
-		LADSPA_Data *data = new LADSPA_Data(0.0);
-		m_controlPortsIn.push_back(
+                LADSPA_Data *data = new LADSPA_Data(0.0);
+                m_controlPortsIn.push_back(
                     std::pair<unsigned long, LADSPA_Data*>(i, data));
 
-	    } else {
+            } else {
 
 #ifdef DEBUG_LADSPA
-		SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is control out" << endl;
+                SVDEBUG << "LADSPAPluginInstance::init: port " << i << " is control out" << endl;
 #endif
-		LADSPA_Data *data = new LADSPA_Data(0.0);
-		m_controlPortsOut.push_back(
+                LADSPA_Data *data = new LADSPA_Data(0.0);
+                m_controlPortsOut.push_back(
                     std::pair<unsigned long, LADSPA_Data*>(i, data));
-		if (!strcmp(m_descriptor->PortNames[i], "latency") ||
-		    !strcmp(m_descriptor->PortNames[i], "_latency")) {
+                if (!strcmp(m_descriptor->PortNames[i], "latency") ||
+                    !strcmp(m_descriptor->PortNames[i], "_latency")) {
 #ifdef DEBUG_LADSPA
-		    cerr << "Wooo! We have a latency port!" << endl;
+                    cerr << "Wooo! We have a latency port!" << endl;
 #endif
-		    m_latencyPort = data;
-		}
+                    m_latencyPort = data;
+                }
 
-	    }
+            }
         }
 #ifdef DEBUG_LADSPA
         else
@@ -282,10 +282,10 @@
     m_instanceCount = 1;
 
     if (idealChannelCount > 0) {
-	if (m_audioPortsIn.size() == 1) {
-	    // mono plugin: duplicate it if need be
-	    m_instanceCount = idealChannelCount;
-	}
+        if (m_audioPortsIn.size() == 1) {
+            // mono plugin: duplicate it if need be
+            m_instanceCount = idealChannelCount;
+        }
     }
 }
 
@@ -293,7 +293,7 @@
 LADSPAPluginInstance::getLatency()
 {
     if (m_latencyPort) {
-	if (!m_run) {
+        if (!m_run) {
             for (int i = 0; i < getAudioInputCount(); ++i) {
                 for (int j = 0; j < m_blockSize; ++j) {
                     m_inputBuffers[i][j] = 0.f;
@@ -301,7 +301,7 @@
             }
             run(Vamp::RealTime::zeroTime);
         }
-	if (*m_latencyPort > 0) return (sv_frame_t)*m_latencyPort;
+        if (*m_latencyPort > 0) return (sv_frame_t)*m_latencyPort;
     }
     return 0;
 }
@@ -310,8 +310,8 @@
 LADSPAPluginInstance::silence()
 {
     if (isOK()) {
-	deactivate();
-	activate();
+        deactivate();
+        activate();
     }
 }
 
@@ -319,12 +319,12 @@
 LADSPAPluginInstance::setIdealChannelCount(int channels)
 {
     if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) {
-	silence();
-	return;
+        silence();
+        return;
     }
 
     if (isOK()) {
-	deactivate();
+        deactivate();
     }
 
     //!!! don't we need to reallocate inputBuffers and outputBuffers?
@@ -333,8 +333,8 @@
     m_instanceCount = channels;
     instantiate(m_sampleRate);
     if (isOK()) {
-	connectPorts();
-	activate();
+        connectPorts();
+        activate();
     }
 }
 
@@ -346,7 +346,7 @@
 #endif
 
     if (m_instanceHandles.size() != 0) { // "isOK()"
-	deactivate();
+        deactivate();
     }
 
     cleanup();
@@ -361,15 +361,15 @@
     m_controlPortsOut.clear();
 
     if (m_ownBuffers) {
-	for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) {
-	    delete[] m_inputBuffers[i];
-	}
-	for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) {
-	    delete[] m_outputBuffers[i];
-	}
+        for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) {
+            delete[] m_inputBuffers[i];
+        }
+        for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) {
+            delete[] m_outputBuffers[i];
+        }
 
-	delete[] m_inputBuffers;
-	delete[] m_outputBuffers;
+        delete[] m_inputBuffers;
+        delete[] m_outputBuffers;
     }
 
     m_audioPortsIn.clear();
@@ -388,10 +388,10 @@
 #endif
 
     if (!m_descriptor->instantiate) {
-	cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
-		  << ":" << m_descriptor->Label
-		  << " has no instantiate method!" << endl;
-	return;
+        cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+                  << ":" << m_descriptor->Label
+                  << " has no instantiate method!" << endl;
+        return;
     }
 
     unsigned long pluginRate = (unsigned long)(sampleRate);
@@ -402,8 +402,8 @@
     }
     
     for (int i = 0; i < m_instanceCount; ++i) {
-	m_instanceHandles.push_back
-	    (m_descriptor->instantiate(m_descriptor, pluginRate));
+        m_instanceHandles.push_back
+            (m_descriptor->instantiate(m_descriptor, pluginRate));
     }
 }
 
@@ -413,8 +413,8 @@
     if (!m_descriptor || !m_descriptor->activate) return;
 
     for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
-	 hi != m_instanceHandles.end(); ++hi) {
-	m_descriptor->activate(*hi);
+         hi != m_instanceHandles.end(); ++hi) {
+        m_descriptor->activate(*hi);
     }
 }
 
@@ -430,44 +430,44 @@
     int inbuf = 0, outbuf = 0;
 
     for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
-	 hi != m_instanceHandles.end(); ++hi) {
+         hi != m_instanceHandles.end(); ++hi) {
 
-	for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
-	    m_descriptor->connect_port(*hi,
-				       m_audioPortsIn[i],
-				       (LADSPA_Data *)m_inputBuffers[inbuf]);
-	    ++inbuf;
-	}
+        for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+            m_descriptor->connect_port(*hi,
+                                       m_audioPortsIn[i],
+                                       (LADSPA_Data *)m_inputBuffers[inbuf]);
+            ++inbuf;
+        }
 
-	for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
-	    m_descriptor->connect_port(*hi,
-				       m_audioPortsOut[i],
-				       (LADSPA_Data *)m_outputBuffers[outbuf]);
-	    ++outbuf;
-	}
+        for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+            m_descriptor->connect_port(*hi,
+                                       m_audioPortsOut[i],
+                                       (LADSPA_Data *)m_outputBuffers[outbuf]);
+            ++outbuf;
+        }
 
-	// If there is more than one instance, they all share the same
-	// control port ins (and outs, for the moment, because we
-	// don't actually do anything with the outs anyway -- but they
-	// do have to be connected as the plugin can't know if they're
-	// not and will write to them anyway).
+        // If there is more than one instance, they all share the same
+        // control port ins (and outs, for the moment, because we
+        // don't actually do anything with the outs anyway -- but they
+        // do have to be connected as the plugin can't know if they're
+        // not and will write to them anyway).
 
-	for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
-	    m_descriptor->connect_port(*hi,
-				       m_controlPortsIn[i].first,
-				       m_controlPortsIn[i].second);
+        for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+            m_descriptor->connect_port(*hi,
+                                       m_controlPortsIn[i].first,
+                                       m_controlPortsIn[i].second);
             if (f) {
                 float defaultValue = f->getPortDefault
                     (m_descriptor, m_controlPortsIn[i].first);
                 *m_controlPortsIn[i].second = defaultValue;
             }
-	}
+        }
 
-	for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
-	    m_descriptor->connect_port(*hi,
-				       m_controlPortsOut[i].first,
-				       m_controlPortsOut[i].second);
-	}
+        for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+            m_descriptor->connect_port(*hi,
+                                       m_controlPortsOut[i].first,
+                                       m_controlPortsOut[i].second);
+        }
     }
 }
 
@@ -486,12 +486,12 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	if (value < f->getPortMinimum(m_descriptor, portNumber)) {
-	    value = f->getPortMinimum(m_descriptor, portNumber);
-	}
-	if (value > f->getPortMaximum(m_descriptor, portNumber)) {
-	    value = f->getPortMaximum(m_descriptor, portNumber);
-	}
+        if (value < f->getPortMinimum(m_descriptor, portNumber)) {
+            value = f->getPortMinimum(m_descriptor, portNumber);
+        }
+        if (value > f->getPortMaximum(m_descriptor, portNumber)) {
+            value = f->getPortMaximum(m_descriptor, portNumber);
+        }
     }
 
     (*m_controlPortsIn[parameter].second) = value;
@@ -518,9 +518,9 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	return f->getPortDefault(m_descriptor, m_controlPortsIn[parameter].first);
+        return f->getPortDefault(m_descriptor, m_controlPortsIn[parameter].first);
     } else {
-	return 0.0f;
+        return 0.0f;
     }
 }
 
@@ -531,9 +531,9 @@
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
-	return f->getPortDisplayHint(m_descriptor, m_controlPortsIn[parameter].first);
+        return f->getPortDisplayHint(m_descriptor, m_controlPortsIn[parameter].first);
     } else {
-	return PortHint::NoHint;
+        return PortHint::NoHint;
     }
 }
 
@@ -545,7 +545,7 @@
     if (count == 0) count = m_blockSize;
 
     for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
-	 hi != m_instanceHandles.end(); ++hi) {
+         hi != m_instanceHandles.end(); ++hi) {
 
         m_descriptor->run(*hi, count);
     }
@@ -559,7 +559,7 @@
     if (!m_descriptor || !m_descriptor->deactivate) return;
 
     for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
-	 hi != m_instanceHandles.end(); ++hi) {
+         hi != m_instanceHandles.end(); ++hi) {
         m_descriptor->deactivate(*hi);
     }
 }
@@ -570,15 +570,15 @@
     if (!m_descriptor) return;
 
     if (!m_descriptor->cleanup) {
-	cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
-		  << ":" << m_descriptor->Label
-		  << " has no cleanup method!" << endl;
-	return;
+        cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+                  << ":" << m_descriptor->Label
+                  << " has no cleanup method!" << endl;
+        return;
     }
 
     for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
-	 hi != m_instanceHandles.end(); ++hi) {
-	m_descriptor->cleanup(*hi);
+         hi != m_instanceHandles.end(); ++hi) {
+        m_descriptor->cleanup(*hi);
     }
 
     m_instanceHandles.clear();
--- a/plugin/LADSPAPluginInstance.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/LADSPAPluginInstance.h	Mon Sep 17 13:51:14 2018 +0100
@@ -89,12 +89,12 @@
     // Constructor that creates the buffers internally
     // 
     LADSPAPluginInstance(RealTimePluginFactory *factory,
-			 int client,
-			 QString identifier,
+                         int client,
+                         QString identifier,
                          int position,
-			 sv_samplerate_t sampleRate,
-			 int blockSize,
-			 int idealChannelCount,
+                         sv_samplerate_t sampleRate,
+                         int blockSize,
+                         int idealChannelCount,
                          const LADSPA_Descriptor* descriptor);
 
     void init(int idealChannelCount = 0);
--- a/plugin/NativeVampPluginFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/NativeVampPluginFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -95,8 +95,8 @@
                  QDir::Files | QDir::Readable);
 
         for (unsigned int i = 0; i < dir.count(); ++i) {
-            QString soname = dir.filePath(dir[i]);
-            candidates.push_back({ soname, "" });
+            QString libpath = dir.filePath(dir[i]);
+            candidates.push_back({ libpath, "" });
         }
     }
 
@@ -121,14 +121,14 @@
         
     for (auto candidate : candidates) {
 
-        QString soname = candidate.libraryPath;
+        QString libpath = candidate.libraryPath;
 
-        SVDEBUG << "INFO: Considering candidate Vamp plugin library " << soname << endl;
+        SVDEBUG << "INFO: Considering candidate Vamp plugin library " << libpath << endl;
         
-        void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
+        void *libraryHandle = DLOPEN(libpath, RTLD_LAZY | RTLD_LOCAL);
             
         if (!libraryHandle) {
-            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << libpath << ": " << DLERROR() << endl;
             continue;
         }
 
@@ -136,9 +136,9 @@
             DLSYM(libraryHandle, "vampGetPluginDescriptor");
 
         if (!fn) {
-            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << libpath << endl;
             if (DLCLOSE(libraryHandle) != 0) {
-                SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+                SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
             }
             continue;
         }
@@ -157,12 +157,12 @@
 
             if (known.find(descriptor->identifier) != known.end()) {
                 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
-                     << soname
-                     << " returns the same plugin identifier \""
-                     << descriptor->identifier << "\" at indices "
-                     << known[descriptor->identifier] << " and "
-                     << index << endl;
-                    SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
+                        << libpath
+                        << " returns the same plugin identifier \""
+                        << descriptor->identifier << "\" at indices "
+                        << known[descriptor->identifier] << " and "
+                        << index << endl;
+                SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
                 ok = false;
                 break;
             } else {
@@ -179,8 +179,9 @@
             while ((descriptor = fn(VAMP_API_VERSION, index))) {
 
                 QString id = PluginIdentifier::createIdentifier
-                    ("vamp", soname, descriptor->identifier);
+                    ("vamp", libpath, descriptor->identifier);
                 m_identifiers.push_back(id);
+                m_libraries[id] = libpath;
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
                 cerr << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
 #endif
@@ -189,7 +190,7 @@
         }
             
         if (DLCLOSE(libraryHandle) != 0) {
-            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
         }
     }
 
@@ -231,7 +232,7 @@
             return file;
         }
 
-	for (unsigned int j = 0; j < dir.count(); ++j) {
+        for (unsigned int j = 0; j < dir.count(); ++j) {
             file = dir.filePath(dir[j]);
             if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
 
@@ -304,7 +305,7 @@
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
         cerr << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
 #endif
-	return 0;
+        return 0;
     }
 
     QString found = findPluginFile(soname);
@@ -392,6 +393,12 @@
     return m_taxonomy[identifier];
 }
 
+QString
+NativeVampPluginFactory::getPluginLibraryPath(QString identifier)
+{
+    return m_libraries[identifier];
+}
+
 void
 NativeVampPluginFactory::generateTaxonomy()
 {
@@ -399,42 +406,42 @@
     vector<QString> path;
 
     for (size_t i = 0; i < pluginPath.size(); ++i) {
-	if (pluginPath[i].contains("/lib/")) {
-	    QString p(pluginPath[i]);
+        if (pluginPath[i].contains("/lib/")) {
+            QString p(pluginPath[i]);
             path.push_back(p);
-	    p.replace("/lib/", "/share/");
-	    path.push_back(p);
-	}
-	path.push_back(pluginPath[i]);
+            p.replace("/lib/", "/share/");
+            path.push_back(p);
+        }
+        path.push_back(pluginPath[i]);
     }
 
     for (size_t i = 0; i < path.size(); ++i) {
 
-	QDir dir(path[i], "*.cat");
+        QDir dir(path[i], "*.cat");
 
-//	SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
-	for (unsigned int j = 0; j < dir.count(); ++j) {
+//        SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
+        for (unsigned int j = 0; j < dir.count(); ++j) {
 
-	    QFile file(path[i] + "/" + dir[j]);
+            QFile file(path[i] + "/" + dir[j]);
 
-//	    SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
+//            SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
 
-	    if (file.open(QIODevice::ReadOnly)) {
-//		    cerr << "...opened" << endl;
-		QTextStream stream(&file);
-		QString line;
+            if (file.open(QIODevice::ReadOnly)) {
+//                    cerr << "...opened" << endl;
+                QTextStream stream(&file);
+                QString line;
 
-		while (!stream.atEnd()) {
-		    line = stream.readLine();
-//		    cerr << "line is: \"" << line << "\"" << endl;
-		    QString id = PluginIdentifier::canonicalise
+                while (!stream.atEnd()) {
+                    line = stream.readLine();
+//                    cerr << "line is: \"" << line << "\"" << endl;
+                    QString id = PluginIdentifier::canonicalise
                         (line.section("::", 0, 0));
-		    QString cat = line.section("::", 1, 1);
-		    m_taxonomy[id] = cat;
-//		    cerr << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
-		}
-	    }
-	}
+                    QString cat = line.section("::", 1, 1);
+                    m_taxonomy[id] = cat;
+//                    cerr << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
+                }
+            }
+        }
     }
 }    
 
--- a/plugin/NativeVampPluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/NativeVampPluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -44,17 +44,17 @@
                                             sv_samplerate_t inputSampleRate)
         override;
 
-    /**
-     * Get category metadata about a plugin (without instantiating it).
-     */
     virtual QString getPluginCategory(QString identifier) override;
 
+    virtual QString getPluginLibraryPath(QString identifier) override;
+
 protected:
     QMutex m_mutex;
     std::vector<QString> m_pluginPath;
     std::vector<QString> m_identifiers;
     std::map<QString, QString> m_taxonomy; // identifier -> category string
     std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data (created opportunistically)
+    std::map<QString, QString> m_libraries; // identifier -> full file path
 
     friend class PluginDeletionNotifyAdapter;
     void pluginDeleted(Vamp::Plugin *);
--- a/plugin/PiperVampPluginFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PiperVampPluginFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -28,7 +28,9 @@
 #define CAPNP_LITE 1
 #endif
 
-#include "vamp-client/AutoPlugin.h"
+#include "vamp-client/qt/PiperAutoPlugin.h"
+#include "vamp-client/qt/ProcessQtTransport.h"
+#include "vamp-client/CapnpRRClient.h"
 
 #include <QDir>
 #include <QFile>
@@ -41,9 +43,6 @@
 #include "base/Profiler.h"
 #include "base/HelperExecPath.h"
 
-#include "vamp-client/ProcessQtTransport.h"
-#include "vamp-client/CapnpRRClient.h"
-
 using namespace std;
 
 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
@@ -114,8 +113,7 @@
     Profiler profiler("PiperVampPluginFactory::instantiatePlugin");
 
     if (m_origins.find(identifier) == m_origins.end()) {
-        cerr << "ERROR: No known server for identifier " << identifier << endl;
-        SVDEBUG << "ERROR: No known server for identifier " << identifier << endl;
+        SVCERR << "ERROR: No known server for identifier " << identifier << endl;
         return 0;
     }
     
@@ -124,10 +122,10 @@
         return 0;
     }
 
-    SVDEBUG << "PiperVampPluginFactory: Creating Piper AutoPlugin for server "
+    SVDEBUG << "PiperVampPluginFactory: Creating PiperAutoPlugin for server "
         << m_origins[identifier] << ", identifier " << identifier << endl;
     
-    auto ap = new piper_vamp::client::AutoPlugin
+    auto ap = new piper_vamp::client::PiperAutoPlugin
         (m_origins[identifier].toStdString(),
          psd.pluginKey,
          float(inputSampleRate),
@@ -162,6 +160,27 @@
     }
 }
 
+QString
+PiperVampPluginFactory::getPluginLibraryPath(QString identifier)
+{
+    // What we want to return here is the file path of the library in
+    // which the plugin was actually found -- we want to be paranoid
+    // about that and not just query
+    // Vamp::HostExt::PluginLoader::getLibraryPathForPlugin to return
+    // what the SDK thinks the likely location would be (in case our
+    // search order turns out to have been different)
+
+    QStringList bits = identifier.split(':');
+    if (bits.size() > 1) {
+        QString soname = bits[bits.size() - 2];
+        auto i = m_libraries.find(soname);
+        if (i != m_libraries.end()) {
+            return i->second;
+        }
+    }
+    return QString();
+}
+
 void
 PiperVampPluginFactory::populate(QString &errorMessage)
 {
@@ -198,6 +217,10 @@
             string soname = QFileInfo(c.libraryPath).baseName().toStdString();
             SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
             from.push_back(soname);
+            QString qsoname = QString::fromStdString(soname);
+            if (m_libraries.find(qsoname) == m_libraries.end()) {
+                m_libraries[qsoname] = c.libraryPath;
+            }
         }
     }
 
@@ -230,8 +253,8 @@
     piper_vamp::ListResponse resp;
 
     try {
-        resp = client.listPluginData(req);
-    } catch (piper_vamp::client::ServerCrashed) {
+        resp = client.list(req);
+    } catch (const piper_vamp::client::ServerCrashed &) {
         SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl;
         errorMessage = QObject::tr
             ("External plugin host exited unexpectedly while listing plugins");
--- a/plugin/PiperVampPluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PiperVampPluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -49,10 +49,13 @@
 
     virtual QString getPluginCategory(QString identifier) override;
 
+    virtual QString getPluginLibraryPath(QString identifier) override;
+
 protected:
     QMutex m_mutex;
     QList<HelperExecPath::HelperExec> m_servers; // executable file paths
     std::map<QString, QString> m_origins; // plugin identifier -> server path
+    std::map<QString, QString> m_libraries; // soname -> full file path
     std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data
     std::map<QString, QString> m_taxonomy; // identifier -> category string
 
--- a/plugin/PluginIdentifier.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PluginIdentifier.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -24,8 +24,8 @@
 
 QString
 PluginIdentifier::createIdentifier(QString type,
-				   QString soName,
-				   QString label)
+                                   QString soName,
+                                   QString label)
 {
     QString identifier = type + ":" + QFileInfo(soName).baseName() + ":" + label;
     return identifier;
@@ -41,9 +41,9 @@
 
 void
 PluginIdentifier::parseIdentifier(QString identifier,
-				  QString &type,
-				  QString &soName,
-				  QString &label)
+                                  QString &type,
+                                  QString &soName,
+                                  QString &label)
 {
     type = identifier.section(':', 0, 0);
     soName = identifier.section(':', 1, 1);
@@ -61,7 +61,7 @@
     if (type1 != type2 || label1 != label2) return false;
 
     bool similar = (soName1.section('/', -1).section('.', 0, 0) ==
-		    soName2.section('/', -1).section('.', 0, 0));
+                    soName2.section('/', -1).section('.', 0, 0));
 
     return similar;
 }
--- a/plugin/PluginIdentifier.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PluginIdentifier.h	Mon Sep 17 13:51:14 2018 +0100
@@ -36,7 +36,7 @@
     static QString canonicalise(QString identifier);
 
     static void parseIdentifier(QString identifier,
-				QString &type, QString &soName, QString &label);
+                                QString &type, QString &soName, QString &label);
 
     static bool areIdentifiersSimilar(QString id1, QString id2);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PluginPathSetter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,278 @@
+/* -*- 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 "PluginPathSetter.h"
+
+#include <vamp-hostsdk/PluginHostAdapter.h>
+
+#include "RealTimePluginFactory.h"
+#include "LADSPAPluginFactory.h"
+#include "DSSIPluginFactory.h"
+
+#include <QSettings>
+#include <QMutexLocker>
+
+#include "system/System.h"
+#include "base/Preferences.h"
+#include "base/HelperExecPath.h"
+
+QMutex
+PluginPathSetter::m_mutex;
+
+PluginPathSetter::Paths
+PluginPathSetter::m_defaultPaths;
+
+PluginPathSetter::Paths
+PluginPathSetter::m_environmentPaths;
+
+std::map<QString, QString>
+PluginPathSetter::m_originalEnvValues;
+
+PluginPathSetter::TypeKeys
+PluginPathSetter::m_supportedKeys;
+
+using namespace std;
+
+PluginPathSetter::TypeKeys
+PluginPathSetter::getSupportedKeys()
+{
+    QMutexLocker locker(&m_mutex);
+
+    if (!m_supportedKeys.empty()) {
+        return m_supportedKeys;
+    }
+
+    TypeKeys keys;
+    keys.push_back({ KnownPlugins::VampPlugin, KnownPlugins::FormatNative });
+    
+    bool inProcess = Preferences::getInstance()->getRunPluginsInProcess();
+    HelperExecPath hep(inProcess ?
+                       HelperExecPath::NativeArchitectureOnly :
+                       HelperExecPath::AllInstalled);
+    auto execs = hep.getHelperExecutables("vamp-plugin-load-checker");
+    if (execs.size() > 1) {
+        keys.push_back({
+                KnownPlugins::VampPlugin, KnownPlugins::FormatNonNative32Bit });
+    }
+
+    keys.push_back({ KnownPlugins::LADSPAPlugin, KnownPlugins::FormatNative });
+    keys.push_back({ KnownPlugins::DSSIPlugin, KnownPlugins::FormatNative });
+
+    m_supportedKeys = keys;
+    return keys;
+}
+
+// call with mutex held please
+PluginPathSetter::Paths
+PluginPathSetter::getEnvironmentPathsUncached(const TypeKeys &keys)
+{
+    Paths paths;
+
+    for (auto k: keys) {
+
+        KnownPlugins kp(k.second);
+
+        auto path = kp.getPathFor(k.first);
+        QStringList qPath;
+        for (auto s: path) {
+            qPath.push_back(QString::fromStdString(s));
+        }
+
+        auto var = kp.getPathEnvironmentVariableFor(k.first);
+        QString qVar = QString::fromStdString(var);
+        
+        paths[k] = { qPath, qVar, true };
+    }
+
+    return paths;
+}
+
+PluginPathSetter::Paths
+PluginPathSetter::getDefaultPaths()
+{
+    TypeKeys keys = getSupportedKeys();
+    
+    QMutexLocker locker(&m_mutex);
+
+    Paths paths;
+
+    for (auto k: keys) {
+
+        KnownPlugins kp(k.second);
+
+        auto path = kp.getDefaultPathFor(k.first);
+        QStringList qPath;
+        for (auto s: path) {
+            qPath.push_back(QString::fromStdString(s));
+        }
+
+        auto var = kp.getPathEnvironmentVariableFor(k.first);
+        QString qVar = QString::fromStdString(var);
+        
+        paths[k] = { qPath, qVar, true };
+    }
+
+    return paths;
+}
+
+PluginPathSetter::Paths
+PluginPathSetter::getEnvironmentPaths()
+{
+    TypeKeys keys = getSupportedKeys();
+    
+    QMutexLocker locker(&m_mutex);
+
+    if (!m_environmentPaths.empty()) {
+        return m_environmentPaths;
+    }
+        
+    m_environmentPaths = getEnvironmentPathsUncached(keys);
+    return m_environmentPaths;
+}
+
+QString
+PluginPathSetter::getSettingTagFor(TypeKey tk)
+{
+    string tag = KnownPlugins(tk.second).getTagFor(tk.first);
+    if (tk.second == KnownPlugins::FormatNonNative32Bit) {
+        tag += "-32";
+    }
+    return QString::fromStdString(tag);
+}
+
+PluginPathSetter::Paths
+PluginPathSetter::getPaths()
+{
+    Paths paths = getEnvironmentPaths();
+       
+    QSettings settings;
+    settings.beginGroup("Plugins");
+
+    for (auto p: paths) {
+
+        TypeKey tk = p.first;
+
+        QString settingTag = getSettingTagFor(tk);
+
+        QStringList directories =
+            settings.value(QString("directories-%1").arg(settingTag),
+                           p.second.directories)
+            .toStringList();
+        QString envVariable =
+            settings.value(QString("env-variable-%1").arg(settingTag),
+                           p.second.envVariable)
+            .toString();
+        bool useEnvVariable =
+            settings.value(QString("use-env-variable-%1").arg(settingTag),
+                           p.second.useEnvVariable)
+            .toBool();
+
+        string envVarStr = envVariable.toStdString();
+        string currentValue;
+        (void)getEnvUtf8(envVarStr, currentValue);
+
+        if (currentValue != "" && useEnvVariable) {
+            directories = QString::fromStdString(currentValue).split(
+#ifdef Q_OS_WIN
+               ";"
+#else
+               ":"
+#endif
+                );
+        }
+        
+        paths[tk] = { directories, envVariable, useEnvVariable };
+    }
+
+    settings.endGroup();
+
+    return paths;
+}
+
+void
+PluginPathSetter::savePathSettings(Paths paths)
+{
+    QSettings settings;
+    settings.beginGroup("Plugins");
+
+    for (auto p: paths) {
+        QString settingTag = getSettingTagFor(p.first);
+        settings.setValue(QString("directories-%1").arg(settingTag),
+                          p.second.directories);
+        settings.setValue(QString("env-variable-%1").arg(settingTag),
+                          p.second.envVariable);
+        settings.setValue(QString("use-env-variable-%1").arg(settingTag),
+                          p.second.useEnvVariable);
+    }
+
+    settings.endGroup();
+}
+
+QString
+PluginPathSetter::getOriginalEnvironmentValue(QString envVariable)
+{
+    if (m_originalEnvValues.find(envVariable) != m_originalEnvValues.end()) {
+        return m_originalEnvValues.at(envVariable);
+    } else {
+        return QString();
+    }
+}
+
+void
+PluginPathSetter::initialiseEnvironmentVariables()
+{
+    // Set the relevant environment variables from user configuration,
+    // so that later lookups through the standard APIs will follow the
+    // same paths as we have in the user config
+
+    // First ensure the default paths have been recorded for later, so
+    // we don't erroneously re-read them from the environment
+    // variables we've just set
+    (void)getDefaultPaths();
+    (void)getEnvironmentPaths();
+    
+    Paths paths = getPaths();
+
+    for (auto p: paths) {
+        QString envVariable = p.second.envVariable;
+        string envVarStr = envVariable.toStdString();
+        string currentValue;
+        getEnvUtf8(envVarStr, currentValue);
+        m_originalEnvValues[envVariable] = QString::fromStdString(currentValue);
+        if (currentValue != "" && p.second.useEnvVariable) {
+            // don't override
+            SVDEBUG << "PluginPathSetter: for environment variable "
+                    << envVariable << ", useEnvVariable setting is false; "
+                    << "leaving current value alone: it is \""
+                    << currentValue << "\"" << endl;
+            continue;
+        }
+        QString separator =
+#ifdef Q_OS_WIN
+            ";"
+#else
+            ":"
+#endif
+            ;
+        QString proposedValue = p.second.directories.join(separator);
+        SVDEBUG << "PluginPathSetter: for environment variable "
+                << envVariable << ", useEnvVariable setting is true or "
+                << "variable is currently unset; "
+                << "changing value from \"" << currentValue
+                << "\" to setting preference of \"" << proposedValue
+                << "\"" << endl;
+        putEnvUtf8(envVarStr, proposedValue.toStdString());
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PluginPathSetter.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,85 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_PLUGIN_PATH_SETTER_H
+#define SV_PLUGIN_PATH_SETTER_H
+
+#include <QString>
+#include <QStringList>
+#include <QMutex>
+
+#include <map>
+
+#include "checker/knownplugins.h"
+
+class PluginPathSetter
+{
+public:
+    typedef std::pair<KnownPlugins::PluginType,
+                      KnownPlugins::BinaryFormat> TypeKey;
+
+    typedef std::vector<TypeKey> TypeKeys;
+
+    struct PathConfig {
+        QStringList directories; // Actual list of directories arising
+                                 // from user settings, environment
+                                 // variables, and defaults as
+                                 // appropriate
+        
+        QString envVariable; // Name of env var, e.g. LADSPA_PATH
+        
+        bool useEnvVariable; // True if env variable should override
+                             // any user settings for this
+    };
+
+    typedef std::map<TypeKey, PathConfig> Paths;
+
+    /// Update *_PATH environment variables from the settings, on
+    /// application startup. Must be called exactly once, before any
+    /// of the other functions in this class has been called
+    static void initialiseEnvironmentVariables();
+
+    /// Return default values of paths only, without any environment
+    /// variables or user-defined preferences
+    static Paths getDefaultPaths();
+
+    /// Return paths arising from environment variables only, falling
+    /// back to the defaults, without any user-defined preferences
+    static Paths getEnvironmentPaths();
+
+    /// Return paths arising from user settings + environment
+    /// variables + defaults as appropriate
+    static Paths getPaths();
+
+    /// Save the given paths to the settings
+    static void savePathSettings(Paths paths);
+
+    /// Return the original value observed on startup for the given
+    /// environment variable, if it is one of the variables used by a
+    /// known path config.
+    static QString getOriginalEnvironmentValue(QString envVariable);
+    
+private:
+    static Paths m_defaultPaths;
+    static Paths m_environmentPaths;
+    static std::map<QString, QString> m_originalEnvValues;
+    static TypeKeys m_supportedKeys;
+    static QMutex m_mutex;
+
+    static std::vector<TypeKey> getSupportedKeys();
+    static Paths getEnvironmentPathsUncached(const TypeKeys &keys);
+    static QString getSettingTagFor(TypeKey);
+};
+
+#endif
--- a/plugin/PluginScan.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PluginScan.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -18,11 +18,7 @@
 #include "base/Preferences.h"
 #include "base/HelperExecPath.h"
 
-#ifdef HAVE_PLUGIN_CHECKER_HELPER
-#include "checker/knownplugins.h"
-#else
-class KnownPlugins {};
-#endif
+#include <sstream>
 
 #include <QMutex>
 #include <QCoreApplication>
@@ -57,6 +53,7 @@
     QMutexLocker locker(&m_mutex);
     clear();
     delete m_logger;
+    SVDEBUG << "PluginScan::~PluginScan completed" << endl;
 }
 
 void
@@ -72,7 +69,7 @@
                        HelperExecPath::NativeArchitectureOnly :
                        HelperExecPath::AllInstalled);
 
-    QString helperName("plugin-checker-helper");
+    QString helperName("vamp-plugin-load-checker");
     auto helpers = hep.getHelperExecutables(helperName);
 
     clear();
@@ -91,7 +88,7 @@
 
     for (auto p: helpers) {
         try {
-            KnownPlugins *kp = new KnownPlugins
+            KnownPluginCandidates *kp = new KnownPluginCandidates
                 (p.executable.toStdString(), m_logger);
             if (m_kp.find(p.tag) != m_kp.end()) {
                 SVDEBUG << "WARNING: PluginScan::scan: Duplicate tag " << p.tag
@@ -106,6 +103,7 @@
         }
     }
 
+    SVDEBUG << "PluginScan::scan complete" << endl;
 #endif
 }
 
@@ -149,7 +147,7 @@
 
     for (auto rec: m_kp) {
 
-        KnownPlugins *kp = rec.second;
+        KnownPluginCandidates *kp = rec.second;
         
         auto c = kp->getCandidateLibrariesFor(kpt);
 
@@ -180,6 +178,95 @@
 #endif
 }
 
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+QString
+PluginScan::formatFailureReport(QString tag,
+                                std::vector<PluginCandidates::FailureRec> failures) const
+{
+    int n = int(failures.size());
+    int i = 0;
+
+    std::ostringstream os;
+    
+    os << "<ul>";
+    for (auto f: failures) {
+        os << "<li>" + f.library;
+
+        SVDEBUG << "PluginScan::formatFailureReport: tag is \"" << tag
+                << "\", failure code is " << int(f.code) << ", message is \""
+                << f.message << "\"" << endl;
+        
+        QString userMessage = QString::fromStdString(f.message);
+
+        switch (f.code) {
+
+        case PluginCheckCode::FAIL_LIBRARY_NOT_FOUND:
+            userMessage = QObject::tr("Library file could not be opened");
+            break;
+
+        case PluginCheckCode::FAIL_WRONG_ARCHITECTURE:
+            if (tag == "64" || (sizeof(void *) == 8 && tag == "")) {
+                userMessage = QObject::tr
+                    ("Library has wrong architecture - possibly a 32-bit plugin installed in a 64-bit plugin folder");
+            } else if (tag == "32" || (sizeof(void *) == 4 && tag == "")) {
+                userMessage = QObject::tr
+                    ("Library has wrong architecture - possibly a 64-bit plugin installed in a 32-bit plugin folder");
+            }
+            break;
+
+        case PluginCheckCode::FAIL_DEPENDENCY_MISSING:
+            userMessage = QObject::tr
+                ("Library depends on another library that cannot be found: %1")
+                .arg(userMessage);
+            break;
+
+        case PluginCheckCode::FAIL_NOT_LOADABLE:
+            userMessage = QObject::tr
+                ("Library cannot be loaded: %1").arg(userMessage);
+            break;
+
+        case PluginCheckCode::FAIL_DESCRIPTOR_MISSING:
+            userMessage = QObject::tr
+                ("Not a valid plugin library (no descriptor found)");
+            break;
+
+        case PluginCheckCode::FAIL_NO_PLUGINS:
+            userMessage = QObject::tr
+                ("Library contains no plugins");
+            break;
+
+        case PluginCheckCode::FAIL_OTHER:
+            if (userMessage == "") {
+                userMessage = QObject::tr
+                    ("Unknown error");
+            }
+            break;
+
+        case PluginCheckCode::SUCCESS:
+            // success shouldn't happen here!
+            break;
+        }
+        
+        os << "<br><i>" + userMessage.toStdString() + "</i>";
+        os << "</li>";
+
+        if (n > 10) {
+            if (++i == 5) {
+                os << "<li>";
+                os << QObject::tr("... and %n further failure(s)",
+                                  "", n - i)
+                    .toStdString();
+                os << "</li>";
+                break;
+            }
+        }
+    }
+    os << "</ul>";
+
+    return QString::fromStdString(os.str());
+}
+#endif
+
 QString
 PluginScan::getStartupFailureReport() const
 {
@@ -188,29 +275,32 @@
     QMutexLocker locker(&m_mutex);
 
     if (!m_succeeded) {
-	return QObject::tr("<b>Failed to scan for plugins</b>"
-			   "<p>Failed to scan for plugins at startup. Possibly "
-                           "the plugin checker helper program was not correctly "
+        return QObject::tr("<b>Failed to scan for plugins</b>"
+                           "<p>Failed to scan for plugins at startup. Possibly "
+                           "the plugin checker program was not correctly "
                            "installed alongside %1?</p>")
             .arg(QCoreApplication::applicationName());
     }
     if (m_kp.empty()) {
-	return QObject::tr("<b>Did not scan for plugins</b>"
-			   "<p>Apparently no scan for plugins was attempted "
-			   "(internal error?)</p>");
+        return QObject::tr("<b>Did not scan for plugins</b>"
+                           "<p>Apparently no scan for plugins was attempted "
+                           "(internal error?)</p>");
     }
 
     QString report;
     for (auto kp: m_kp) {
-        report += QString::fromStdString(kp.second->getFailureReport());
+        auto failures = kp.second->getFailures();
+        if (!failures.empty()) {
+            report += formatFailureReport(kp.first, failures);
+        }
     }
     if (report == "") {
-	return report;
+        return report;
     }
 
     return QObject::tr("<b>Failed to load plugins</b>"
-		       "<p>Failed to load one or more plugin libraries:</p>")
-	+ report
+                       "<p>Failed to load one or more plugin libraries:</p>")
+        + report
         + QObject::tr("<p>These plugins may be incompatible with the system, "
                       "and will be ignored during this run of %1.</p>")
         .arg(QCoreApplication::applicationName());
--- a/plugin/PluginScan.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/PluginScan.h	Mon Sep 17 13:51:14 2018 +0100
@@ -20,7 +20,11 @@
 #include <vector>
 #include <map>
 
-class KnownPlugins;
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+#include "checker/knownplugincandidates.h"
+#else
+class KnownPluginCandidates {};
+#endif
 
 class PluginScan
 {
@@ -45,9 +49,9 @@
     bool scanSucceeded() const;
     
     enum PluginType {
-	VampPlugin,
-	LADSPAPlugin,
-	DSSIPlugin
+        VampPlugin,
+        LADSPAPlugin,
+        DSSIPlugin
     };
     struct Candidate {
         QString libraryPath;    // full path, not just soname
@@ -73,9 +77,15 @@
 
     void clear();
 
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+    QString formatFailureReport(QString helperTag,
+                                std::vector<PluginCandidates::FailureRec>)
+        const;
+#endif
+
     mutable QMutex m_mutex; // while scanning; definitely can't multi-thread this
     
-    std::map<QString, KnownPlugins *> m_kp; // tag -> KnownPlugins client
+    std::map<QString, KnownPluginCandidates *> m_kp; // tag -> KnownPlugins client
     bool m_succeeded;
 
     class Logger;
--- a/plugin/RealTimePluginFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/RealTimePluginFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -43,21 +43,21 @@
 RealTimePluginFactory::instance(QString pluginType)
 {
     if (pluginType == "ladspa") {
-	if (!_ladspaInstance) {
-//	    SVDEBUG << "RealTimePluginFactory::instance(" << pluginType//		      << "): creating new LADSPAPluginFactory" << endl;
-	    _ladspaInstance = new LADSPAPluginFactory();
-	    _ladspaInstance->discoverPlugins();
-	}
-	return _ladspaInstance;
+        if (!_ladspaInstance) {
+//            SVDEBUG << "RealTimePluginFactory::instance(" << pluginType//                      << "): creating new LADSPAPluginFactory" << endl;
+            _ladspaInstance = new LADSPAPluginFactory();
+            _ladspaInstance->discoverPlugins();
+        }
+        return _ladspaInstance;
     } else if (pluginType == "dssi") {
-	if (!_dssiInstance) {
-//	    SVDEBUG << "RealTimePluginFactory::instance(" << pluginType//		      << "): creating new DSSIPluginFactory" << endl;
-	    _dssiInstance = new DSSIPluginFactory();
-	    _dssiInstance->discoverPlugins();
-	}
-	return _dssiInstance;
+        if (!_dssiInstance) {
+//            SVDEBUG << "RealTimePluginFactory::instance(" << pluginType//                      << "): creating new DSSIPluginFactory" << endl;
+            _dssiInstance = new DSSIPluginFactory();
+            _dssiInstance->discoverPlugins();
+        }
+        return _dssiInstance;
     }
-	
+        
     else return 0;
 }
 
@@ -86,18 +86,18 @@
 
     factory = instance("dssi");
     if (factory) {
-	const std::vector<QString> &tmp = factory->getPluginIdentifiers();
-	for (size_t i = 0; i < tmp.size(); ++i) {
-	    rv.push_back(tmp[i]);
-	}
+        const std::vector<QString> &tmp = factory->getPluginIdentifiers();
+        for (size_t i = 0; i < tmp.size(); ++i) {
+            rv.push_back(tmp[i]);
+        }
     }
 
     factory = instance("ladspa");
     if (factory) {
-	const std::vector<QString> &tmp = factory->getPluginIdentifiers();
-	for (size_t i = 0; i < tmp.size(); ++i) {
-	    rv.push_back(tmp[i]);
-	}
+        const std::vector<QString> &tmp = factory->getPluginIdentifiers();
+        for (size_t i = 0; i < tmp.size(); ++i) {
+            rv.push_back(tmp[i]);
+        }
     }
 
     // Plugins can change the locale, revert it to default.
--- a/plugin/RealTimePluginFactory.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/RealTimePluginFactory.h	Mon Sep 17 13:51:14 2018 +0100
@@ -86,17 +86,25 @@
      * Instantiate a plugin.
      */
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
-						      int clientId,
-						      int position,
-						      sv_samplerate_t sampleRate,
-						      int blockSize,
-						      int channels) = 0;
+                                                      int clientId,
+                                                      int position,
+                                                      sv_samplerate_t sampleRate,
+                                                      int blockSize,
+                                                      int channels) = 0;
 
     /**
      * Get category metadata about a plugin (without instantiating it).
      */
     virtual QString getPluginCategory(QString identifier) = 0;
 
+    /**
+     * Get the full file path (including both directory and filename)
+     * of the library file that provides a given plugin
+     * identifier. Note getPluginIdentifiers() must have been called
+     * before this has access to the necessary information.
+     */
+    virtual QString getPluginLibraryPath(QString identifier) = 0;
+    
 protected:
     RealTimePluginFactory() { }
 
--- a/plugin/RealTimePluginInstance.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/RealTimePluginInstance.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -31,9 +31,9 @@
 //    SVDEBUG << "RealTimePluginInstance::~RealTimePluginInstance" << endl;
 
     if (m_factory) {
-//	SVDEBUG << "Asking factory to release " << m_identifier << endl;
+//        SVDEBUG << "Asking factory to release " << m_identifier << endl;
 
-	m_factory->releasePlugin(this, m_identifier);
+        m_factory->releasePlugin(this, m_identifier);
     }
 }
 
--- a/plugin/RealTimePluginInstance.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/RealTimePluginInstance.h	Mon Sep 17 13:51:14 2018 +0100
@@ -33,7 +33,7 @@
 #include <map>
 
 class RealTimePluginFactory;
-	
+        
 /**
  * RealTimePluginInstance is an interface that an audio process can
  * use to refer to an instance of a plugin without needing to know
@@ -121,7 +121,7 @@
     virtual std::string configure(std::string /* key */, std::string /* value */) { return std::string(); }
 
     virtual void sendEvent(const RealTime & /* eventTime */,
-			   const void * /* event */) { }
+                           const void * /* event */) { }
     virtual void clearEvents() { }
 
     virtual bool isBypassed() const = 0;
@@ -145,7 +145,7 @@
 
 protected:
     RealTimePluginInstance(RealTimePluginFactory *factory, QString identifier) :
-	m_factory(factory), m_identifier(identifier) { }
+        m_factory(factory), m_identifier(identifier) { }
 
     RealTimePluginFactory *m_factory;
     QString m_identifier;
--- a/plugin/api/alsa/asoundef.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/api/alsa/asoundef.h	Mon Sep 17 13:51:14 2018 +0100
@@ -44,8 +44,8 @@
  * \{
  */
 
-#define MIDI_CHANNELS			16	/**< Number of channels per port/cable. */
-#define MIDI_GM_DRUM_CHANNEL		(10-1)	/**< Channel number for GM drums. */
+#define MIDI_CHANNELS                        16        /**< Number of channels per port/cable. */
+#define MIDI_GM_DRUM_CHANNEL                (10-1)        /**< Channel number for GM drums. */
 
 /**
  * \defgroup MIDI_Commands MIDI Commands
@@ -53,26 +53,26 @@
  * \{
  */
 
-#define MIDI_CMD_NOTE_OFF		0x80	/**< note off */
-#define MIDI_CMD_NOTE_ON		0x90	/**< note on */
-#define MIDI_CMD_NOTE_PRESSURE		0xa0	/**< key pressure */
-#define MIDI_CMD_CONTROL		0xb0	/**< control change */
-#define MIDI_CMD_PGM_CHANGE		0xc0	/**< program change */
-#define MIDI_CMD_CHANNEL_PRESSURE	0xd0	/**< channel pressure */
-#define MIDI_CMD_BENDER			0xe0	/**< pitch bender */
+#define MIDI_CMD_NOTE_OFF                0x80        /**< note off */
+#define MIDI_CMD_NOTE_ON                0x90        /**< note on */
+#define MIDI_CMD_NOTE_PRESSURE                0xa0        /**< key pressure */
+#define MIDI_CMD_CONTROL                0xb0        /**< control change */
+#define MIDI_CMD_PGM_CHANGE                0xc0        /**< program change */
+#define MIDI_CMD_CHANNEL_PRESSURE        0xd0        /**< channel pressure */
+#define MIDI_CMD_BENDER                        0xe0        /**< pitch bender */
 
-#define MIDI_CMD_COMMON_SYSEX		0xf0	/**< sysex (system exclusive) begin */
-#define MIDI_CMD_COMMON_MTC_QUARTER	0xf1	/**< MTC quarter frame */
-#define MIDI_CMD_COMMON_SONG_POS	0xf2	/**< song position */
-#define MIDI_CMD_COMMON_SONG_SELECT	0xf3	/**< song select */
-#define MIDI_CMD_COMMON_TUNE_REQUEST	0xf6	/**< tune request */
-#define MIDI_CMD_COMMON_SYSEX_END	0xf7	/**< end of sysex */
-#define MIDI_CMD_COMMON_CLOCK		0xf8	/**< clock */
-#define MIDI_CMD_COMMON_START		0xfa	/**< start */
-#define MIDI_CMD_COMMON_CONTINUE	0xfb	/**< continue */
-#define MIDI_CMD_COMMON_STOP		0xfc	/**< stop */
-#define MIDI_CMD_COMMON_SENSING		0xfe	/**< active sensing */
-#define MIDI_CMD_COMMON_RESET		0xff	/**< reset */
+#define MIDI_CMD_COMMON_SYSEX                0xf0        /**< sysex (system exclusive) begin */
+#define MIDI_CMD_COMMON_MTC_QUARTER        0xf1        /**< MTC quarter frame */
+#define MIDI_CMD_COMMON_SONG_POS        0xf2        /**< song position */
+#define MIDI_CMD_COMMON_SONG_SELECT        0xf3        /**< song select */
+#define MIDI_CMD_COMMON_TUNE_REQUEST        0xf6        /**< tune request */
+#define MIDI_CMD_COMMON_SYSEX_END        0xf7        /**< end of sysex */
+#define MIDI_CMD_COMMON_CLOCK                0xf8        /**< clock */
+#define MIDI_CMD_COMMON_START                0xfa        /**< start */
+#define MIDI_CMD_COMMON_CONTINUE        0xfb        /**< continue */
+#define MIDI_CMD_COMMON_STOP                0xfc        /**< stop */
+#define MIDI_CMD_COMMON_SENSING                0xfe        /**< active sensing */
+#define MIDI_CMD_COMMON_RESET                0xff        /**< reset */
 
 /** \} */
 
@@ -82,78 +82,78 @@
  * \{
  */
 
-#define MIDI_CTL_MSB_BANK		0x00	/**< Bank selection */
-#define MIDI_CTL_MSB_MODWHEEL         	0x01	/**< Modulation */
-#define MIDI_CTL_MSB_BREATH           	0x02	/**< Breath */
-#define MIDI_CTL_MSB_FOOT             	0x04	/**< Foot */
-#define MIDI_CTL_MSB_PORTAMENTO_TIME 	0x05	/**< Portamento time */
-#define MIDI_CTL_MSB_DATA_ENTRY		0x06	/**< Data entry */
-#define MIDI_CTL_MSB_MAIN_VOLUME      	0x07	/**< Main volume */
-#define MIDI_CTL_MSB_BALANCE          	0x08	/**< Balance */
-#define MIDI_CTL_MSB_PAN              	0x0a	/**< Panpot */
-#define MIDI_CTL_MSB_EXPRESSION       	0x0b	/**< Expression */
-#define MIDI_CTL_MSB_EFFECT1		0x0c	/**< Effect1 */
-#define MIDI_CTL_MSB_EFFECT2		0x0d	/**< Effect2 */
-#define MIDI_CTL_MSB_GENERAL_PURPOSE1 	0x10	/**< General purpose 1 */
-#define MIDI_CTL_MSB_GENERAL_PURPOSE2 	0x11	/**< General purpose 2 */
-#define MIDI_CTL_MSB_GENERAL_PURPOSE3 	0x12	/**< General purpose 3 */
-#define MIDI_CTL_MSB_GENERAL_PURPOSE4 	0x13	/**< General purpose 4 */
-#define MIDI_CTL_LSB_BANK		0x20	/**< Bank selection */
-#define MIDI_CTL_LSB_MODWHEEL        	0x21	/**< Modulation */
-#define MIDI_CTL_LSB_BREATH           	0x22	/**< Breath */
-#define MIDI_CTL_LSB_FOOT             	0x24	/**< Foot */
-#define MIDI_CTL_LSB_PORTAMENTO_TIME 	0x25	/**< Portamento time */
-#define MIDI_CTL_LSB_DATA_ENTRY		0x26	/**< Data entry */
-#define MIDI_CTL_LSB_MAIN_VOLUME      	0x27	/**< Main volume */
-#define MIDI_CTL_LSB_BALANCE          	0x28	/**< Balance */
-#define MIDI_CTL_LSB_PAN              	0x2a	/**< Panpot */
-#define MIDI_CTL_LSB_EXPRESSION       	0x2b	/**< Expression */
-#define MIDI_CTL_LSB_EFFECT1		0x2c	/**< Effect1 */
-#define MIDI_CTL_LSB_EFFECT2		0x2d	/**< Effect2 */
-#define MIDI_CTL_LSB_GENERAL_PURPOSE1 	0x30	/**< General purpose 1 */
-#define MIDI_CTL_LSB_GENERAL_PURPOSE2 	0x31	/**< General purpose 2 */
-#define MIDI_CTL_LSB_GENERAL_PURPOSE3 	0x32	/**< General purpose 3 */
-#define MIDI_CTL_LSB_GENERAL_PURPOSE4 	0x33	/**< General purpose 4 */
-#define MIDI_CTL_SUSTAIN              	0x40	/**< Sustain pedal */
-#define MIDI_CTL_PORTAMENTO           	0x41	/**< Portamento */
-#define MIDI_CTL_SUSTENUTO            	0x42	/**< Sostenuto */
-#define MIDI_CTL_SOFT_PEDAL           	0x43	/**< Soft pedal */
-#define MIDI_CTL_LEGATO_FOOTSWITCH	0x44	/**< Legato foot switch */
-#define MIDI_CTL_HOLD2                	0x45	/**< Hold2 */
-#define MIDI_CTL_SC1_SOUND_VARIATION	0x46	/**< SC1 Sound Variation */
-#define MIDI_CTL_SC2_TIMBRE		0x47	/**< SC2 Timbre */
-#define MIDI_CTL_SC3_RELEASE_TIME	0x48	/**< SC3 Release Time */
-#define MIDI_CTL_SC4_ATTACK_TIME	0x49	/**< SC4 Attack Time */
-#define MIDI_CTL_SC5_BRIGHTNESS		0x4a	/**< SC5 Brightness */
-#define MIDI_CTL_SC6			0x4b	/**< SC6 */
-#define MIDI_CTL_SC7			0x4c	/**< SC7 */
-#define MIDI_CTL_SC8			0x4d	/**< SC8 */
-#define MIDI_CTL_SC9			0x4e	/**< SC9 */
-#define MIDI_CTL_SC10			0x4f	/**< SC10 */
-#define MIDI_CTL_GENERAL_PURPOSE5     	0x50	/**< General purpose 5 */
-#define MIDI_CTL_GENERAL_PURPOSE6     	0x51	/**< General purpose 6 */
-#define MIDI_CTL_GENERAL_PURPOSE7     	0x52	/**< General purpose 7 */
-#define MIDI_CTL_GENERAL_PURPOSE8     	0x53	/**< General purpose 8 */
-#define MIDI_CTL_PORTAMENTO_CONTROL	0x54	/**< Portamento control */
-#define MIDI_CTL_E1_REVERB_DEPTH	0x5b	/**< E1 Reverb Depth */
-#define MIDI_CTL_E2_TREMOLO_DEPTH	0x5c	/**< E2 Tremolo Depth */
-#define MIDI_CTL_E3_CHORUS_DEPTH	0x5d	/**< E3 Chorus Depth */
-#define MIDI_CTL_E4_DETUNE_DEPTH	0x5e	/**< E4 Detune Depth */
-#define MIDI_CTL_E5_PHASER_DEPTH	0x5f	/**< E5 Phaser Depth */
-#define MIDI_CTL_DATA_INCREMENT       	0x60	/**< Data Increment */
-#define MIDI_CTL_DATA_DECREMENT       	0x61	/**< Data Decrement */
-#define MIDI_CTL_NONREG_PARM_NUM_LSB  	0x62	/**< Non-registered parameter number */
-#define MIDI_CTL_NONREG_PARM_NUM_MSB  	0x63	/**< Non-registered parameter number */
-#define MIDI_CTL_REGIST_PARM_NUM_LSB  	0x64	/**< Registered parameter number */
-#define MIDI_CTL_REGIST_PARM_NUM_MSB	0x65	/**< Registered parameter number */
-#define MIDI_CTL_ALL_SOUNDS_OFF		0x78	/**< All sounds off */
-#define MIDI_CTL_RESET_CONTROLLERS	0x79	/**< Reset Controllers */
-#define MIDI_CTL_LOCAL_CONTROL_SWITCH	0x7a	/**< Local control switch */
-#define MIDI_CTL_ALL_NOTES_OFF		0x7b	/**< All notes off */
-#define MIDI_CTL_OMNI_OFF		0x7c	/**< Omni off */
-#define MIDI_CTL_OMNI_ON		0x7d	/**< Omni on */
-#define MIDI_CTL_MONO1			0x7e	/**< Mono1 */
-#define MIDI_CTL_MONO2			0x7f	/**< Mono2 */
+#define MIDI_CTL_MSB_BANK                0x00        /**< Bank selection */
+#define MIDI_CTL_MSB_MODWHEEL                 0x01        /**< Modulation */
+#define MIDI_CTL_MSB_BREATH                   0x02        /**< Breath */
+#define MIDI_CTL_MSB_FOOT                     0x04        /**< Foot */
+#define MIDI_CTL_MSB_PORTAMENTO_TIME         0x05        /**< Portamento time */
+#define MIDI_CTL_MSB_DATA_ENTRY                0x06        /**< Data entry */
+#define MIDI_CTL_MSB_MAIN_VOLUME              0x07        /**< Main volume */
+#define MIDI_CTL_MSB_BALANCE                  0x08        /**< Balance */
+#define MIDI_CTL_MSB_PAN                      0x0a        /**< Panpot */
+#define MIDI_CTL_MSB_EXPRESSION               0x0b        /**< Expression */
+#define MIDI_CTL_MSB_EFFECT1                0x0c        /**< Effect1 */
+#define MIDI_CTL_MSB_EFFECT2                0x0d        /**< Effect2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE1         0x10        /**< General purpose 1 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE2         0x11        /**< General purpose 2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE3         0x12        /**< General purpose 3 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE4         0x13        /**< General purpose 4 */
+#define MIDI_CTL_LSB_BANK                0x20        /**< Bank selection */
+#define MIDI_CTL_LSB_MODWHEEL                0x21        /**< Modulation */
+#define MIDI_CTL_LSB_BREATH                   0x22        /**< Breath */
+#define MIDI_CTL_LSB_FOOT                     0x24        /**< Foot */
+#define MIDI_CTL_LSB_PORTAMENTO_TIME         0x25        /**< Portamento time */
+#define MIDI_CTL_LSB_DATA_ENTRY                0x26        /**< Data entry */
+#define MIDI_CTL_LSB_MAIN_VOLUME              0x27        /**< Main volume */
+#define MIDI_CTL_LSB_BALANCE                  0x28        /**< Balance */
+#define MIDI_CTL_LSB_PAN                      0x2a        /**< Panpot */
+#define MIDI_CTL_LSB_EXPRESSION               0x2b        /**< Expression */
+#define MIDI_CTL_LSB_EFFECT1                0x2c        /**< Effect1 */
+#define MIDI_CTL_LSB_EFFECT2                0x2d        /**< Effect2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE1         0x30        /**< General purpose 1 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE2         0x31        /**< General purpose 2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE3         0x32        /**< General purpose 3 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE4         0x33        /**< General purpose 4 */
+#define MIDI_CTL_SUSTAIN                      0x40        /**< Sustain pedal */
+#define MIDI_CTL_PORTAMENTO                   0x41        /**< Portamento */
+#define MIDI_CTL_SUSTENUTO                    0x42        /**< Sostenuto */
+#define MIDI_CTL_SOFT_PEDAL                   0x43        /**< Soft pedal */
+#define MIDI_CTL_LEGATO_FOOTSWITCH        0x44        /**< Legato foot switch */
+#define MIDI_CTL_HOLD2                        0x45        /**< Hold2 */
+#define MIDI_CTL_SC1_SOUND_VARIATION        0x46        /**< SC1 Sound Variation */
+#define MIDI_CTL_SC2_TIMBRE                0x47        /**< SC2 Timbre */
+#define MIDI_CTL_SC3_RELEASE_TIME        0x48        /**< SC3 Release Time */
+#define MIDI_CTL_SC4_ATTACK_TIME        0x49        /**< SC4 Attack Time */
+#define MIDI_CTL_SC5_BRIGHTNESS                0x4a        /**< SC5 Brightness */
+#define MIDI_CTL_SC6                        0x4b        /**< SC6 */
+#define MIDI_CTL_SC7                        0x4c        /**< SC7 */
+#define MIDI_CTL_SC8                        0x4d        /**< SC8 */
+#define MIDI_CTL_SC9                        0x4e        /**< SC9 */
+#define MIDI_CTL_SC10                        0x4f        /**< SC10 */
+#define MIDI_CTL_GENERAL_PURPOSE5             0x50        /**< General purpose 5 */
+#define MIDI_CTL_GENERAL_PURPOSE6             0x51        /**< General purpose 6 */
+#define MIDI_CTL_GENERAL_PURPOSE7             0x52        /**< General purpose 7 */
+#define MIDI_CTL_GENERAL_PURPOSE8             0x53        /**< General purpose 8 */
+#define MIDI_CTL_PORTAMENTO_CONTROL        0x54        /**< Portamento control */
+#define MIDI_CTL_E1_REVERB_DEPTH        0x5b        /**< E1 Reverb Depth */
+#define MIDI_CTL_E2_TREMOLO_DEPTH        0x5c        /**< E2 Tremolo Depth */
+#define MIDI_CTL_E3_CHORUS_DEPTH        0x5d        /**< E3 Chorus Depth */
+#define MIDI_CTL_E4_DETUNE_DEPTH        0x5e        /**< E4 Detune Depth */
+#define MIDI_CTL_E5_PHASER_DEPTH        0x5f        /**< E5 Phaser Depth */
+#define MIDI_CTL_DATA_INCREMENT               0x60        /**< Data Increment */
+#define MIDI_CTL_DATA_DECREMENT               0x61        /**< Data Decrement */
+#define MIDI_CTL_NONREG_PARM_NUM_LSB          0x62        /**< Non-registered parameter number */
+#define MIDI_CTL_NONREG_PARM_NUM_MSB          0x63        /**< Non-registered parameter number */
+#define MIDI_CTL_REGIST_PARM_NUM_LSB          0x64        /**< Registered parameter number */
+#define MIDI_CTL_REGIST_PARM_NUM_MSB        0x65        /**< Registered parameter number */
+#define MIDI_CTL_ALL_SOUNDS_OFF                0x78        /**< All sounds off */
+#define MIDI_CTL_RESET_CONTROLLERS        0x79        /**< Reset Controllers */
+#define MIDI_CTL_LOCAL_CONTROL_SWITCH        0x7a        /**< Local control switch */
+#define MIDI_CTL_ALL_NOTES_OFF                0x7b        /**< All notes off */
+#define MIDI_CTL_OMNI_OFF                0x7c        /**< Omni off */
+#define MIDI_CTL_OMNI_ON                0x7d        /**< Omni on */
+#define MIDI_CTL_MONO1                        0x7e        /**< Mono1 */
+#define MIDI_CTL_MONO2                        0x7f        /**< Mono2 */
 
 /** \} */
 
--- a/plugin/api/alsa/seq.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/api/alsa/seq.h	Mon Sep 17 13:51:14 2018 +0100
@@ -48,34 +48,34 @@
 
 /* event type macros */
 enum {
-	SND_SEQ_EVFLG_RESULT,
-	SND_SEQ_EVFLG_NOTE,
-	SND_SEQ_EVFLG_CONTROL,
-	SND_SEQ_EVFLG_QUEUE,
-	SND_SEQ_EVFLG_SYSTEM,
-	SND_SEQ_EVFLG_MESSAGE,
-	SND_SEQ_EVFLG_CONNECTION,
-	SND_SEQ_EVFLG_SAMPLE,
-	SND_SEQ_EVFLG_USERS,
-	SND_SEQ_EVFLG_INSTR,
-	SND_SEQ_EVFLG_QUOTE,
-	SND_SEQ_EVFLG_NONE,
-	SND_SEQ_EVFLG_RAW,
-	SND_SEQ_EVFLG_FIXED,
-	SND_SEQ_EVFLG_VARIABLE,
-	SND_SEQ_EVFLG_VARUSR
+        SND_SEQ_EVFLG_RESULT,
+        SND_SEQ_EVFLG_NOTE,
+        SND_SEQ_EVFLG_CONTROL,
+        SND_SEQ_EVFLG_QUEUE,
+        SND_SEQ_EVFLG_SYSTEM,
+        SND_SEQ_EVFLG_MESSAGE,
+        SND_SEQ_EVFLG_CONNECTION,
+        SND_SEQ_EVFLG_SAMPLE,
+        SND_SEQ_EVFLG_USERS,
+        SND_SEQ_EVFLG_INSTR,
+        SND_SEQ_EVFLG_QUOTE,
+        SND_SEQ_EVFLG_NONE,
+        SND_SEQ_EVFLG_RAW,
+        SND_SEQ_EVFLG_FIXED,
+        SND_SEQ_EVFLG_VARIABLE,
+        SND_SEQ_EVFLG_VARUSR
 };
 
 enum {
-	SND_SEQ_EVFLG_NOTE_ONEARG,
-	SND_SEQ_EVFLG_NOTE_TWOARG
+        SND_SEQ_EVFLG_NOTE_ONEARG,
+        SND_SEQ_EVFLG_NOTE_TWOARG
 };
 
 enum {
-	SND_SEQ_EVFLG_QUEUE_NOARG,
-	SND_SEQ_EVFLG_QUEUE_TICK,
-	SND_SEQ_EVFLG_QUEUE_TIME,
-	SND_SEQ_EVFLG_QUEUE_VALUE
+        SND_SEQ_EVFLG_QUEUE_NOARG,
+        SND_SEQ_EVFLG_QUEUE_TICK,
+        SND_SEQ_EVFLG_QUEUE_TIME,
+        SND_SEQ_EVFLG_QUEUE_VALUE
 };
 
 /**
@@ -85,99 +85,99 @@
  */
 extern const unsigned int snd_seq_event_types[];
 
-#define _SND_SEQ_TYPE(x)	(1<<(x))	/**< master type - 24bit */
-#define _SND_SEQ_TYPE_OPT(x)	((x)<<24)	/**< optional type - 8bit */
+#define _SND_SEQ_TYPE(x)        (1<<(x))        /**< master type - 24bit */
+#define _SND_SEQ_TYPE_OPT(x)        ((x)<<24)        /**< optional type - 8bit */
 
 /** check the event type */
 #define snd_seq_type_check(ev,x) (snd_seq_event_types[(ev)->type] & _SND_SEQ_TYPE(x))
 
 /** event type check: result events */
 #define snd_seq_ev_is_result_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_RESULT)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_RESULT)
 /** event type check: note events */
 #define snd_seq_ev_is_note_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_NOTE)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_NOTE)
 /** event type check: control events */
 #define snd_seq_ev_is_control_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_CONTROL)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_CONTROL)
 /** event type check: channel specific events */
 #define snd_seq_ev_is_channel_type(ev) \
-	(snd_seq_event_types[(ev)->type] & (_SND_SEQ_TYPE(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_CONTROL)))
+        (snd_seq_event_types[(ev)->type] & (_SND_SEQ_TYPE(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_CONTROL)))
 
 /** event type check: queue control events */
 #define snd_seq_ev_is_queue_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_QUEUE)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_QUEUE)
 /** event type check: system status messages */
 #define snd_seq_ev_is_message_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_MESSAGE)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_MESSAGE)
 /** event type check: system status messages */
 #define snd_seq_ev_is_subscribe_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_CONNECTION)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_CONNECTION)
 /** event type check: sample messages */
 #define snd_seq_ev_is_sample_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_SAMPLE)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_SAMPLE)
 /** event type check: user-defined messages */
 #define snd_seq_ev_is_user_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_USERS)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_USERS)
 /** event type check: instrument layer events */
 #define snd_seq_ev_is_instr_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_INSTR)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_INSTR)
 /** event type check: fixed length events */
 #define snd_seq_ev_is_fixed_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_FIXED)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_FIXED)
 /** event type check: variable length events */
-#define snd_seq_ev_is_variable_type(ev)	\
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_VARIABLE)
+#define snd_seq_ev_is_variable_type(ev)        \
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_VARIABLE)
 /** event type check: user pointer events */
 #define snd_seq_ev_is_varusr_type(ev) \
-	snd_seq_type_check(ev, SND_SEQ_EVFLG_VARUSR)
+        snd_seq_type_check(ev, SND_SEQ_EVFLG_VARUSR)
 /** event type check: reserved for kernel */
 #define snd_seq_ev_is_reserved(ev) \
-	(! snd_seq_event_types[(ev)->type])
+        (! snd_seq_event_types[(ev)->type])
 
 /**
  * macros to check event flags
  */
 /** prior events */
-#define snd_seq_ev_is_prior(ev)	\
-	(((ev)->flags & SND_SEQ_PRIORITY_MASK) == SND_SEQ_PRIORITY_HIGH)
+#define snd_seq_ev_is_prior(ev)        \
+        (((ev)->flags & SND_SEQ_PRIORITY_MASK) == SND_SEQ_PRIORITY_HIGH)
 
 /** get the data length type */
 #define snd_seq_ev_length_type(ev) \
-	((ev)->flags & SND_SEQ_EVENT_LENGTH_MASK)
+        ((ev)->flags & SND_SEQ_EVENT_LENGTH_MASK)
 /** fixed length events */
-#define snd_seq_ev_is_fixed(ev)	\
-	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_FIXED)
+#define snd_seq_ev_is_fixed(ev)        \
+        (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_FIXED)
 /** variable length events */
 #define snd_seq_ev_is_variable(ev) \
-	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARIABLE)
+        (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARIABLE)
 /** variable length on user-space */
 #define snd_seq_ev_is_varusr(ev) \
-	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARUSR)
+        (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARUSR)
 
 /** time-stamp type */
 #define snd_seq_ev_timestamp_type(ev) \
-	((ev)->flags & SND_SEQ_TIME_STAMP_MASK)
+        ((ev)->flags & SND_SEQ_TIME_STAMP_MASK)
 /** event is in tick time */
 #define snd_seq_ev_is_tick(ev) \
-	(snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_TICK)
+        (snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_TICK)
 /** event is in real-time */
 #define snd_seq_ev_is_real(ev) \
-	(snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_REAL)
+        (snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_REAL)
 
 /** time-mode type */
 #define snd_seq_ev_timemode_type(ev) \
-	((ev)->flags & SND_SEQ_TIME_MODE_MASK)
+        ((ev)->flags & SND_SEQ_TIME_MODE_MASK)
 /** scheduled in absolute time */
 #define snd_seq_ev_is_abstime(ev) \
-	(snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_ABS)
+        (snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_ABS)
 /** scheduled in relative time */
 #define snd_seq_ev_is_reltime(ev) \
-	(snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_REL)
+        (snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_REL)
 
 /** direct dispatched events */
 #define snd_seq_ev_is_direct(ev) \
-	((ev)->queue == SND_SEQ_QUEUE_DIRECT)
+        ((ev)->queue == SND_SEQ_QUEUE_DIRECT)
 
 /** \} */
 
--- a/plugin/api/alsa/seq_event.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/api/alsa/seq_event.h	Mon Sep 17 13:51:14 2018 +0100
@@ -48,213 +48,213 @@
 
 /** Sequencer event type */
 enum snd_seq_event_type {
-	/** system status; event data type = #snd_seq_result_t */
-	SND_SEQ_EVENT_SYSTEM = 0,
-	/** returned result status; event data type = #snd_seq_result_t */
-	SND_SEQ_EVENT_RESULT,
+        /** system status; event data type = #snd_seq_result_t */
+        SND_SEQ_EVENT_SYSTEM = 0,
+        /** returned result status; event data type = #snd_seq_result_t */
+        SND_SEQ_EVENT_RESULT,
 
-	/** note on and off with duration; event data type = #snd_seq_ev_note_t */
-	SND_SEQ_EVENT_NOTE = 5,
-	/** note on; event data type = #snd_seq_ev_note_t */
-	SND_SEQ_EVENT_NOTEON,
-	/** note off; event data type = #snd_seq_ev_note_t */
-	SND_SEQ_EVENT_NOTEOFF,
-	/** key pressure change (aftertouch); event data type = #snd_seq_ev_note_t */
-	SND_SEQ_EVENT_KEYPRESS,
-	
-	/** controller; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_CONTROLLER = 10,
-	/** program change; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_PGMCHANGE,
-	/** channel pressure; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_CHANPRESS,
-	/** pitchwheel; event data type = #snd_seq_ev_ctrl_t; data is from -8192 to 8191) */
-	SND_SEQ_EVENT_PITCHBEND,
-	/** 14 bit controller value; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_CONTROL14,
-	/** 14 bit NRPN;  event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_NONREGPARAM,
-	/** 14 bit RPN; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_REGPARAM,
+        /** note on and off with duration; event data type = #snd_seq_ev_note_t */
+        SND_SEQ_EVENT_NOTE = 5,
+        /** note on; event data type = #snd_seq_ev_note_t */
+        SND_SEQ_EVENT_NOTEON,
+        /** note off; event data type = #snd_seq_ev_note_t */
+        SND_SEQ_EVENT_NOTEOFF,
+        /** key pressure change (aftertouch); event data type = #snd_seq_ev_note_t */
+        SND_SEQ_EVENT_KEYPRESS,
+        
+        /** controller; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_CONTROLLER = 10,
+        /** program change; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_PGMCHANGE,
+        /** channel pressure; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_CHANPRESS,
+        /** pitchwheel; event data type = #snd_seq_ev_ctrl_t; data is from -8192 to 8191) */
+        SND_SEQ_EVENT_PITCHBEND,
+        /** 14 bit controller value; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_CONTROL14,
+        /** 14 bit NRPN;  event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_NONREGPARAM,
+        /** 14 bit RPN; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_REGPARAM,
 
-	/** SPP with LSB and MSB values; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_SONGPOS = 20,
-	/** Song Select with song ID number; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_SONGSEL,
-	/** midi time code quarter frame; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_QFRAME,
-	/** SMF Time Signature event; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_TIMESIGN,
-	/** SMF Key Signature event; event data type = #snd_seq_ev_ctrl_t */
-	SND_SEQ_EVENT_KEYSIGN,
-	        
-	/** MIDI Real Time Start message; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_START = 30,
-	/** MIDI Real Time Continue message; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_CONTINUE,
-	/** MIDI Real Time Stop message; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_STOP,
-	/** Set tick queue position; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_SETPOS_TICK,
-	/** Set real-time queue position; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_SETPOS_TIME,
-	/** (SMF) Tempo event; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_TEMPO,
-	/** MIDI Real Time Clock message; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_CLOCK,
-	/** MIDI Real Time Tick message; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_TICK,
-	/** Queue timer skew; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_QUEUE_SKEW,
-	/** Sync position changed; event data type = #snd_seq_ev_queue_control_t */
-	SND_SEQ_EVENT_SYNC_POS,
+        /** SPP with LSB and MSB values; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_SONGPOS = 20,
+        /** Song Select with song ID number; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_SONGSEL,
+        /** midi time code quarter frame; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_QFRAME,
+        /** SMF Time Signature event; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_TIMESIGN,
+        /** SMF Key Signature event; event data type = #snd_seq_ev_ctrl_t */
+        SND_SEQ_EVENT_KEYSIGN,
+                
+        /** MIDI Real Time Start message; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_START = 30,
+        /** MIDI Real Time Continue message; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_CONTINUE,
+        /** MIDI Real Time Stop message; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_STOP,
+        /** Set tick queue position; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_SETPOS_TICK,
+        /** Set real-time queue position; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_SETPOS_TIME,
+        /** (SMF) Tempo event; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_TEMPO,
+        /** MIDI Real Time Clock message; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_CLOCK,
+        /** MIDI Real Time Tick message; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_TICK,
+        /** Queue timer skew; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_QUEUE_SKEW,
+        /** Sync position changed; event data type = #snd_seq_ev_queue_control_t */
+        SND_SEQ_EVENT_SYNC_POS,
 
-	/** Tune request; event data type = none */
-	SND_SEQ_EVENT_TUNE_REQUEST = 40,
-	/** Reset to power-on state; event data type = none */
-	SND_SEQ_EVENT_RESET,
-	/** Active sensing event; event data type = none */
-	SND_SEQ_EVENT_SENSING,
+        /** Tune request; event data type = none */
+        SND_SEQ_EVENT_TUNE_REQUEST = 40,
+        /** Reset to power-on state; event data type = none */
+        SND_SEQ_EVENT_RESET,
+        /** Active sensing event; event data type = none */
+        SND_SEQ_EVENT_SENSING,
 
-	/** Echo-back event; event data type = any type */
-	SND_SEQ_EVENT_ECHO = 50,
-	/** OSS emulation raw event; event data type = any type */
-	SND_SEQ_EVENT_OSS,
+        /** Echo-back event; event data type = any type */
+        SND_SEQ_EVENT_ECHO = 50,
+        /** OSS emulation raw event; event data type = any type */
+        SND_SEQ_EVENT_OSS,
 
-	/** New client has connected; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_CLIENT_START = 60,
-	/** Client has left the system; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_CLIENT_EXIT,
-	/** Client status/info has changed; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_CLIENT_CHANGE,
-	/** New port was created; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_PORT_START,
-	/** Port was deleted from system; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_PORT_EXIT,
-	/** Port status/info has changed; event data type = #snd_seq_addr_t */
-	SND_SEQ_EVENT_PORT_CHANGE,
+        /** New client has connected; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_CLIENT_START = 60,
+        /** Client has left the system; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_CLIENT_EXIT,
+        /** Client status/info has changed; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_CLIENT_CHANGE,
+        /** New port was created; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_PORT_START,
+        /** Port was deleted from system; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_PORT_EXIT,
+        /** Port status/info has changed; event data type = #snd_seq_addr_t */
+        SND_SEQ_EVENT_PORT_CHANGE,
 
-	/** Ports connected; event data type = #snd_seq_connect_t */
-	SND_SEQ_EVENT_PORT_SUBSCRIBED,
-	/** Ports disconnected; event data type = #snd_seq_connect_t */
-	SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
+        /** Ports connected; event data type = #snd_seq_connect_t */
+        SND_SEQ_EVENT_PORT_SUBSCRIBED,
+        /** Ports disconnected; event data type = #snd_seq_connect_t */
+        SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
 
-	/** Sample select; event data type = #snd_seq_ev_sample_control_t */
-	SND_SEQ_EVENT_SAMPLE = 70,
-	/** Sample cluster select; event data type = #snd_seq_ev_sample_control_t */
-	SND_SEQ_EVENT_SAMPLE_CLUSTER,
-	/** voice start */
-	SND_SEQ_EVENT_SAMPLE_START,
-	/** voice stop */
-	SND_SEQ_EVENT_SAMPLE_STOP,
-	/** playback frequency */
-	SND_SEQ_EVENT_SAMPLE_FREQ,
-	/** volume and balance */
-	SND_SEQ_EVENT_SAMPLE_VOLUME,
-	/** sample loop */
-	SND_SEQ_EVENT_SAMPLE_LOOP,
-	/** sample position */
-	SND_SEQ_EVENT_SAMPLE_POSITION,
-	/** private (hardware dependent) event */
-	SND_SEQ_EVENT_SAMPLE_PRIVATE1,
+        /** Sample select; event data type = #snd_seq_ev_sample_control_t */
+        SND_SEQ_EVENT_SAMPLE = 70,
+        /** Sample cluster select; event data type = #snd_seq_ev_sample_control_t */
+        SND_SEQ_EVENT_SAMPLE_CLUSTER,
+        /** voice start */
+        SND_SEQ_EVENT_SAMPLE_START,
+        /** voice stop */
+        SND_SEQ_EVENT_SAMPLE_STOP,
+        /** playback frequency */
+        SND_SEQ_EVENT_SAMPLE_FREQ,
+        /** volume and balance */
+        SND_SEQ_EVENT_SAMPLE_VOLUME,
+        /** sample loop */
+        SND_SEQ_EVENT_SAMPLE_LOOP,
+        /** sample position */
+        SND_SEQ_EVENT_SAMPLE_POSITION,
+        /** private (hardware dependent) event */
+        SND_SEQ_EVENT_SAMPLE_PRIVATE1,
 
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR0 = 90,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR1,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR2,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR3,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR4,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR5,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR6,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR7,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR8,
-	/** user-defined event; event data type = any (fixed size) */
-	SND_SEQ_EVENT_USR9,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR0 = 90,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR1,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR2,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR3,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR4,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR5,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR6,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR7,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR8,
+        /** user-defined event; event data type = any (fixed size) */
+        SND_SEQ_EVENT_USR9,
 
-	/** begin of instrument management */
-	SND_SEQ_EVENT_INSTR_BEGIN = 100,
-	/** end of instrument management */
-	SND_SEQ_EVENT_INSTR_END,
-	/** query instrument interface info */
-	SND_SEQ_EVENT_INSTR_INFO,
-	/** result of instrument interface info */
-	SND_SEQ_EVENT_INSTR_INFO_RESULT,
-	/** query instrument format info */
-	SND_SEQ_EVENT_INSTR_FINFO,
-	/** result of instrument format info */
-	SND_SEQ_EVENT_INSTR_FINFO_RESULT,
-	/** reset instrument instrument memory */
-	SND_SEQ_EVENT_INSTR_RESET,
-	/** get instrument interface status */
-	SND_SEQ_EVENT_INSTR_STATUS,
-	/** result of instrument interface status */
-	SND_SEQ_EVENT_INSTR_STATUS_RESULT,
-	/** put an instrument to port */
-	SND_SEQ_EVENT_INSTR_PUT,
-	/** get an instrument from port */
-	SND_SEQ_EVENT_INSTR_GET,
-	/** result of instrument query */
-	SND_SEQ_EVENT_INSTR_GET_RESULT,
-	/** free instrument(s) */
-	SND_SEQ_EVENT_INSTR_FREE,
-	/** get instrument list */
-	SND_SEQ_EVENT_INSTR_LIST,
-	/** result of instrument list */
-	SND_SEQ_EVENT_INSTR_LIST_RESULT,
-	/** set cluster parameters */
-	SND_SEQ_EVENT_INSTR_CLUSTER,
-	/** get cluster parameters */
-	SND_SEQ_EVENT_INSTR_CLUSTER_GET,
-	/** result of cluster parameters */
-	SND_SEQ_EVENT_INSTR_CLUSTER_RESULT,
-	/** instrument change */
-	SND_SEQ_EVENT_INSTR_CHANGE,
+        /** begin of instrument management */
+        SND_SEQ_EVENT_INSTR_BEGIN = 100,
+        /** end of instrument management */
+        SND_SEQ_EVENT_INSTR_END,
+        /** query instrument interface info */
+        SND_SEQ_EVENT_INSTR_INFO,
+        /** result of instrument interface info */
+        SND_SEQ_EVENT_INSTR_INFO_RESULT,
+        /** query instrument format info */
+        SND_SEQ_EVENT_INSTR_FINFO,
+        /** result of instrument format info */
+        SND_SEQ_EVENT_INSTR_FINFO_RESULT,
+        /** reset instrument instrument memory */
+        SND_SEQ_EVENT_INSTR_RESET,
+        /** get instrument interface status */
+        SND_SEQ_EVENT_INSTR_STATUS,
+        /** result of instrument interface status */
+        SND_SEQ_EVENT_INSTR_STATUS_RESULT,
+        /** put an instrument to port */
+        SND_SEQ_EVENT_INSTR_PUT,
+        /** get an instrument from port */
+        SND_SEQ_EVENT_INSTR_GET,
+        /** result of instrument query */
+        SND_SEQ_EVENT_INSTR_GET_RESULT,
+        /** free instrument(s) */
+        SND_SEQ_EVENT_INSTR_FREE,
+        /** get instrument list */
+        SND_SEQ_EVENT_INSTR_LIST,
+        /** result of instrument list */
+        SND_SEQ_EVENT_INSTR_LIST_RESULT,
+        /** set cluster parameters */
+        SND_SEQ_EVENT_INSTR_CLUSTER,
+        /** get cluster parameters */
+        SND_SEQ_EVENT_INSTR_CLUSTER_GET,
+        /** result of cluster parameters */
+        SND_SEQ_EVENT_INSTR_CLUSTER_RESULT,
+        /** instrument change */
+        SND_SEQ_EVENT_INSTR_CHANGE,
 
-	/** system exclusive data (variable length);  event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_SYSEX = 130,
-	/** error event;  event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_BOUNCE,
-	/** reserved for user apps;  event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_USR_VAR0 = 135,
-	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_USR_VAR1,
-	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_USR_VAR2,
-	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_USR_VAR3,
-	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
-	SND_SEQ_EVENT_USR_VAR4,
+        /** system exclusive data (variable length);  event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_SYSEX = 130,
+        /** error event;  event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_BOUNCE,
+        /** reserved for user apps;  event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_USR_VAR0 = 135,
+        /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_USR_VAR1,
+        /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_USR_VAR2,
+        /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_USR_VAR3,
+        /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+        SND_SEQ_EVENT_USR_VAR4,
 
-	/** NOP; ignored in any case */
-	SND_SEQ_EVENT_NONE = 255
+        /** NOP; ignored in any case */
+        SND_SEQ_EVENT_NONE = 255
 };
 
 
 /** Sequencer event address */
 typedef struct snd_seq_addr {
-	unsigned char client;	/**< Client id */
-	unsigned char port;	/**< Port id */
+        unsigned char client;        /**< Client id */
+        unsigned char port;        /**< Port id */
 } snd_seq_addr_t;
 
 /** Connection (subscription) between ports */
 typedef struct snd_seq_connect {
-	snd_seq_addr_t sender;	/**< sender address */
-	snd_seq_addr_t dest;	/**< destination address */
+        snd_seq_addr_t sender;        /**< sender address */
+        snd_seq_addr_t dest;        /**< destination address */
 } snd_seq_connect_t;
 
 
 /** Real-time data record */
 typedef struct snd_seq_real_time {
-	unsigned int tv_sec;		/**< seconds */
-	unsigned int tv_nsec;		/**< nanoseconds */
+        unsigned int tv_sec;                /**< seconds */
+        unsigned int tv_nsec;                /**< nanoseconds */
 } snd_seq_real_time_t;
 
 /** (MIDI) Tick-time data record */
@@ -262,8 +262,8 @@
 
 /** unioned time stamp */
 typedef union snd_seq_timestamp {
-	snd_seq_tick_time_t tick;	/**< tick-time */
-	struct snd_seq_real_time time;	/**< real-time */
+        snd_seq_tick_time_t tick;        /**< tick-time */
+        struct snd_seq_real_time time;        /**< real-time */
 } snd_seq_timestamp_t;
 
 
@@ -272,55 +272,55 @@
  *
  * NOTE: only 8 bits available!
  */
-#define SND_SEQ_TIME_STAMP_TICK		(0<<0)	/**< timestamp in clock ticks */
-#define SND_SEQ_TIME_STAMP_REAL		(1<<0)	/**< timestamp in real time */
-#define SND_SEQ_TIME_STAMP_MASK		(1<<0)	/**< mask for timestamp bits */
+#define SND_SEQ_TIME_STAMP_TICK                (0<<0)        /**< timestamp in clock ticks */
+#define SND_SEQ_TIME_STAMP_REAL                (1<<0)        /**< timestamp in real time */
+#define SND_SEQ_TIME_STAMP_MASK                (1<<0)        /**< mask for timestamp bits */
 
-#define SND_SEQ_TIME_MODE_ABS		(0<<1)	/**< absolute timestamp */
-#define SND_SEQ_TIME_MODE_REL		(1<<1)	/**< relative to current time */
-#define SND_SEQ_TIME_MODE_MASK		(1<<1)	/**< mask for time mode bits */
+#define SND_SEQ_TIME_MODE_ABS                (0<<1)        /**< absolute timestamp */
+#define SND_SEQ_TIME_MODE_REL                (1<<1)        /**< relative to current time */
+#define SND_SEQ_TIME_MODE_MASK                (1<<1)        /**< mask for time mode bits */
 
-#define SND_SEQ_EVENT_LENGTH_FIXED	(0<<2)	/**< fixed event size */
-#define SND_SEQ_EVENT_LENGTH_VARIABLE	(1<<2)	/**< variable event size */
-#define SND_SEQ_EVENT_LENGTH_VARUSR	(2<<2)	/**< variable event size - user memory space */
-#define SND_SEQ_EVENT_LENGTH_MASK	(3<<2)	/**< mask for event length bits */
+#define SND_SEQ_EVENT_LENGTH_FIXED        (0<<2)        /**< fixed event size */
+#define SND_SEQ_EVENT_LENGTH_VARIABLE        (1<<2)        /**< variable event size */
+#define SND_SEQ_EVENT_LENGTH_VARUSR        (2<<2)        /**< variable event size - user memory space */
+#define SND_SEQ_EVENT_LENGTH_MASK        (3<<2)        /**< mask for event length bits */
 
-#define SND_SEQ_PRIORITY_NORMAL		(0<<4)	/**< normal priority */
-#define SND_SEQ_PRIORITY_HIGH		(1<<4)	/**< event should be processed before others */
-#define SND_SEQ_PRIORITY_MASK		(1<<4)	/**< mask for priority bits */
+#define SND_SEQ_PRIORITY_NORMAL                (0<<4)        /**< normal priority */
+#define SND_SEQ_PRIORITY_HIGH                (1<<4)        /**< event should be processed before others */
+#define SND_SEQ_PRIORITY_MASK                (1<<4)        /**< mask for priority bits */
 
 
 /** Note event */
 typedef struct snd_seq_ev_note {
-	unsigned char channel;		/**< channel number */
-	unsigned char note;		/**< note */
-	unsigned char velocity;		/**< velocity */
-	unsigned char off_velocity;	/**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */
-	unsigned int duration;		/**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */
+        unsigned char channel;                /**< channel number */
+        unsigned char note;                /**< note */
+        unsigned char velocity;                /**< velocity */
+        unsigned char off_velocity;        /**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */
+        unsigned int duration;                /**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */
 } snd_seq_ev_note_t;
 
 /** Controller event */
 typedef struct snd_seq_ev_ctrl {
-	unsigned char channel;		/**< channel number */
-	unsigned char unused[3];	/**< reserved */
-	unsigned int param;		/**< control parameter */
-	signed int value;		/**< control value */
+        unsigned char channel;                /**< channel number */
+        unsigned char unused[3];        /**< reserved */
+        unsigned int param;                /**< control parameter */
+        signed int value;                /**< control value */
 } snd_seq_ev_ctrl_t;
 
 /** generic set of bytes (12x8 bit) */
 typedef struct snd_seq_ev_raw8 {
-	unsigned char d[12];		/**< 8 bit value */
+        unsigned char d[12];                /**< 8 bit value */
 } snd_seq_ev_raw8_t;
 
 /** generic set of integers (3x32 bit) */
 typedef struct snd_seq_ev_raw32 {
-	unsigned int d[3];		/**< 32 bit value */
+        unsigned int d[3];                /**< 32 bit value */
 } snd_seq_ev_raw32_t;
 
 /** external stored data */
 typedef struct snd_seq_ev_ext {
-	unsigned int len;		/**< length of data */
-	void *ptr;			/**< pointer to data (note: can be 64-bit) */
+        unsigned int len;                /**< length of data */
+        void *ptr;                        /**< pointer to data (note: can be 64-bit) */
 }
 #ifdef __GNUC__
 __attribute__((packed))
@@ -332,22 +332,22 @@
 
 /** Instrument type */
 typedef struct snd_seq_instr {
-	snd_seq_instr_cluster_t cluster;	/**< cluster id */
-	unsigned int std;	/**< instrument standard id; the upper byte means a private instrument (owner - client id) */
-	unsigned short bank;	/**< instrument bank id */
-	unsigned short prg;	/**< instrument program id */
+        snd_seq_instr_cluster_t cluster;        /**< cluster id */
+        unsigned int std;        /**< instrument standard id; the upper byte means a private instrument (owner - client id) */
+        unsigned short bank;        /**< instrument bank id */
+        unsigned short prg;        /**< instrument program id */
 } snd_seq_instr_t;
 
 /** sample number */
 typedef struct snd_seq_ev_sample {
-	unsigned int std;	/**< sample standard id */
-	unsigned short bank;	/**< sample bank id */
-	unsigned short prg;	/**< sample program id */
+        unsigned int std;        /**< sample standard id */
+        unsigned short bank;        /**< sample bank id */
+        unsigned short prg;        /**< sample program id */
 } snd_seq_ev_sample_t;
 
 /** sample cluster */
 typedef struct snd_seq_ev_cluster {
-	snd_seq_instr_cluster_t cluster;	/**< cluster id */
+        snd_seq_instr_cluster_t cluster;        /**< cluster id */
 } snd_seq_ev_cluster_t;
 
 /** sample position */
@@ -355,9 +355,9 @@
 
 /** sample stop mode */
 typedef enum snd_seq_stop_mode {
-	SND_SEQ_SAMPLE_STOP_IMMEDIATELY = 0,	/**< terminate playing immediately */
-	SND_SEQ_SAMPLE_STOP_VENVELOPE = 1,	/**< finish volume envelope */
-	SND_SEQ_SAMPLE_STOP_LOOP = 2		/**< terminate loop and finish wave */
+        SND_SEQ_SAMPLE_STOP_IMMEDIATELY = 0,        /**< terminate playing immediately */
+        SND_SEQ_SAMPLE_STOP_VENVELOPE = 1,        /**< finish volume envelope */
+        SND_SEQ_SAMPLE_STOP_LOOP = 2                /**< terminate loop and finish wave */
 } snd_seq_stop_mode_t;
 
 /** sample frequency */
@@ -365,94 +365,94 @@
 
 /** sample volume control; if any value is set to -1 == do not change */
 typedef struct snd_seq_ev_volume {
-	signed short volume;	/**< range: 0-16383 */
-	signed short lr;	/**< left-right balance; range: 0-16383 */
-	signed short fr;	/**< front-rear balance; range: 0-16383 */
-	signed short du;	/**< down-up balance; range: 0-16383 */
+        signed short volume;        /**< range: 0-16383 */
+        signed short lr;        /**< left-right balance; range: 0-16383 */
+        signed short fr;        /**< front-rear balance; range: 0-16383 */
+        signed short du;        /**< down-up balance; range: 0-16383 */
 } snd_seq_ev_volume_t;
 
 /** simple loop redefinition */
 typedef struct snd_seq_ev_loop {
-	unsigned int start;	/**< loop start (in samples) * 16 */
-	unsigned int end;	/**< loop end (in samples) * 16 */
+        unsigned int start;        /**< loop start (in samples) * 16 */
+        unsigned int end;        /**< loop end (in samples) * 16 */
 } snd_seq_ev_loop_t;
 
 /** Sample control events */
 typedef struct snd_seq_ev_sample_control {
-	unsigned char channel;		/**< channel */
-	unsigned char unused[3];	/**< reserved */
-	union {
-		snd_seq_ev_sample_t sample;	/**< sample number */
-		snd_seq_ev_cluster_t cluster;	/**< cluster number */
-		snd_seq_position_t position;	/**< position */
-		snd_seq_stop_mode_t stop_mode;	/**< stop mode */
-		snd_seq_frequency_t frequency;	/**< frequency */
-		snd_seq_ev_volume_t volume;	/**< volume */
-		snd_seq_ev_loop_t loop;		/**< loop control */
-		unsigned char raw8[8];		/**< raw 8-bit */
-	} param;		/**< control parameters */
+        unsigned char channel;                /**< channel */
+        unsigned char unused[3];        /**< reserved */
+        union {
+                snd_seq_ev_sample_t sample;        /**< sample number */
+                snd_seq_ev_cluster_t cluster;        /**< cluster number */
+                snd_seq_position_t position;        /**< position */
+                snd_seq_stop_mode_t stop_mode;        /**< stop mode */
+                snd_seq_frequency_t frequency;        /**< frequency */
+                snd_seq_ev_volume_t volume;        /**< volume */
+                snd_seq_ev_loop_t loop;                /**< loop control */
+                unsigned char raw8[8];                /**< raw 8-bit */
+        } param;                /**< control parameters */
 } snd_seq_ev_sample_control_t;
 
 
 
 /** INSTR_BEGIN event */
 typedef struct snd_seq_ev_instr_begin {
-	int timeout;		/**< zero = forever, otherwise timeout in ms */
+        int timeout;                /**< zero = forever, otherwise timeout in ms */
 } snd_seq_ev_instr_begin_t;
 
 /** Result events */
 typedef struct snd_seq_result {
-	int event;		/**< processed event type */
-	int result;		/**< status */
+        int event;                /**< processed event type */
+        int result;                /**< status */
 } snd_seq_result_t;
 
 /** Queue skew values */
 typedef struct snd_seq_queue_skew {
-	unsigned int value;	/**< skew value */
-	unsigned int base;	/**< skew base */
+        unsigned int value;        /**< skew value */
+        unsigned int base;        /**< skew base */
 } snd_seq_queue_skew_t;
 
 /** queue timer control */
 typedef struct snd_seq_ev_queue_control {
-	unsigned char queue;			/**< affected queue */
-	unsigned char unused[3];		/**< reserved */
-	union {
-		signed int value;		/**< affected value (e.g. tempo) */
-		snd_seq_timestamp_t time;	/**< time */
-		unsigned int position;		/**< sync position */
-		snd_seq_queue_skew_t skew;	/**< queue skew */
-		unsigned int d32[2];		/**< any data */
-		unsigned char d8[8];		/**< any data */
-	} param;				/**< data value union */
+        unsigned char queue;                        /**< affected queue */
+        unsigned char unused[3];                /**< reserved */
+        union {
+                signed int value;                /**< affected value (e.g. tempo) */
+                snd_seq_timestamp_t time;        /**< time */
+                unsigned int position;                /**< sync position */
+                snd_seq_queue_skew_t skew;        /**< queue skew */
+                unsigned int d32[2];                /**< any data */
+                unsigned char d8[8];                /**< any data */
+        } param;                                /**< data value union */
 } snd_seq_ev_queue_control_t;
 
 
 /** Sequencer event */
 typedef struct snd_seq_event {
-	snd_seq_event_type_t type;	/**< event type */
-	unsigned char flags;		/**< event flags */
-	unsigned char tag;		/**< tag */
-	
-	unsigned char queue;		/**< schedule queue */
-	snd_seq_timestamp_t time;	/**< schedule time */
+        snd_seq_event_type_t type;        /**< event type */
+        unsigned char flags;                /**< event flags */
+        unsigned char tag;                /**< tag */
+        
+        unsigned char queue;                /**< schedule queue */
+        snd_seq_timestamp_t time;        /**< schedule time */
 
-	snd_seq_addr_t source;		/**< source address */
-	snd_seq_addr_t dest;		/**< destination address */
+        snd_seq_addr_t source;                /**< source address */
+        snd_seq_addr_t dest;                /**< destination address */
 
-	union {
-		snd_seq_ev_note_t note;		/**< note information */
-		snd_seq_ev_ctrl_t control;	/**< MIDI control information */
-		snd_seq_ev_raw8_t raw8;		/**< raw8 data */
-		snd_seq_ev_raw32_t raw32;	/**< raw32 data */
-		snd_seq_ev_ext_t ext;		/**< external data */
-		snd_seq_ev_queue_control_t queue; /**< queue control */
-		snd_seq_timestamp_t time;	/**< timestamp */
-		snd_seq_addr_t addr;		/**< address */
-		snd_seq_connect_t connect;	/**< connect information */
-		snd_seq_result_t result;	/**< operation result code */
-		snd_seq_ev_instr_begin_t instr_begin; /**< instrument */
-		snd_seq_ev_sample_control_t sample; /**< sample control */
-	} data;				/**< event data... */
+        union {
+                snd_seq_ev_note_t note;                /**< note information */
+                snd_seq_ev_ctrl_t control;        /**< MIDI control information */
+                snd_seq_ev_raw8_t raw8;                /**< raw8 data */
+                snd_seq_ev_raw32_t raw32;        /**< raw32 data */
+                snd_seq_ev_ext_t ext;                /**< external data */
+                snd_seq_ev_queue_control_t queue; /**< queue control */
+                snd_seq_timestamp_t time;        /**< timestamp */
+                snd_seq_addr_t addr;                /**< address */
+                snd_seq_connect_t connect;        /**< connect information */
+                snd_seq_result_t result;        /**< operation result code */
+                snd_seq_ev_instr_begin_t instr_begin; /**< instrument */
+                snd_seq_ev_sample_control_t sample; /**< sample control */
+        } data;                                /**< event data... */
 } snd_seq_event_t;
 
 
--- a/plugin/api/alsa/sound/asequencer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/api/alsa/sound/asequencer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -28,11 +28,11 @@
 #ifndef __SOUND_ASEQUENCER_H
 #define __SOUND_ASEQUENCER_H
 
-#define SNDRV_SEQ_EVENT_SYSEX		130	/* system exclusive data (variable length) */
+#define SNDRV_SEQ_EVENT_SYSEX                130        /* system exclusive data (variable length) */
 
-#define SNDRV_SEQ_EVENT_LENGTH_FIXED	(0<<2)	/* fixed event size */
-#define SNDRV_SEQ_EVENT_LENGTH_VARIABLE	(1<<2)	/* variable event size */
+#define SNDRV_SEQ_EVENT_LENGTH_FIXED        (0<<2)        /* fixed event size */
+#define SNDRV_SEQ_EVENT_LENGTH_VARIABLE        (1<<2)        /* variable event size */
 
-#define SNDRV_SEQ_EVENT_LENGTH_MASK	(3<<2)
+#define SNDRV_SEQ_EVENT_LENGTH_MASK        (3<<2)
 
 #endif /* __SOUND_ASEQUENCER_H */
--- a/plugin/api/dssi.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/api/dssi.h	Mon Sep 17 13:51:14 2018 +0100
@@ -71,9 +71,9 @@
     unsigned long Bank;
 
     /** Program number (unique within its bank) for this program.
-	There is no restriction on the set of available programs: the
-	numbers do not need to be contiguous, there does not need to
-	be a program 0, etc. */
+        There is no restriction on the set of available programs: the
+        numbers do not need to be contiguous, there does not need to
+        be a program 0, etc. */
     unsigned long Program;
 
     /** Name of the program. */
@@ -248,13 +248,13 @@
      * See also the configure OSC call documentation in RFC.txt.
      */
     char *(*configure)(LADSPA_Handle Instance,
-		       const char *Key,
-		       const char *Value);
+                       const char *Key,
+                       const char *Value);
 
     #define DSSI_RESERVED_CONFIGURE_PREFIX "DSSI:"
     #define DSSI_GLOBAL_CONFIGURE_PREFIX "GLOBAL:"
     #define DSSI_PROJECT_DIRECTORY_KEY \
-	DSSI_RESERVED_CONFIGURE_PREFIX "PROJECT_DIRECTORY"
+        DSSI_RESERVED_CONFIGURE_PREFIX "PROJECT_DIRECTORY"
 
     /**
      * get_program()
@@ -278,7 +278,7 @@
      * programs as well as their properties.
      */
     const DSSI_Program_Descriptor *(*get_program)(LADSPA_Handle Instance,
-						  unsigned long Index);
+                                                  unsigned long Index);
     
     /**
      * select_program()
@@ -308,8 +308,8 @@
      * which a DSSI plugin is allowed to modify its own input ports.)
      */
     void (*select_program)(LADSPA_Handle Instance,
-			   unsigned long Bank,
-			   unsigned long Program);
+                           unsigned long Bank,
+                           unsigned long Program);
 
     /**
      * get_midi_controller_for_port()
@@ -338,7 +338,7 @@
      * controllers 0 or 32 (MIDI Bank Select MSB and LSB).
      */
     int (*get_midi_controller_for_port)(LADSPA_Handle Instance,
-					unsigned long Port);
+                                        unsigned long Port);
 
     /**
      * run_synth()
@@ -388,9 +388,9 @@
      * select controller to a plugin via run_synth.
      */
     void (*run_synth)(LADSPA_Handle    Instance,
-		      unsigned long    SampleCount,
-		      snd_seq_event_t *Events,
-		      unsigned long    EventCount);
+                      unsigned long    SampleCount,
+                      snd_seq_event_t *Events,
+                      unsigned long    EventCount);
 
     /**
      * run_synth_adding()
@@ -402,9 +402,9 @@
      * that does not provide it must set this member to NULL.
      */
     void (*run_synth_adding)(LADSPA_Handle    Instance,
-			     unsigned long    SampleCount,
-			     snd_seq_event_t *Events,
-			     unsigned long    EventCount);
+                             unsigned long    SampleCount,
+                             snd_seq_event_t *Events,
+                             unsigned long    EventCount);
 
     /**
      * run_multiple_synths()
@@ -609,7 +609,7 @@
     */
 
     int (*request_non_rt_thread)(LADSPA_Handle Instance,
-				 void (*RunFunction)(LADSPA_Handle Instance));
+                                 void (*RunFunction)(LADSPA_Handle Instance));
 };
 
 /**
@@ -641,19 +641,19 @@
  * get_midi_controller_for_port()
  */
 
-#define DSSI_CC_BITS			0x20000000
-#define DSSI_NRPN_BITS			0x40000000
+#define DSSI_CC_BITS                        0x20000000
+#define DSSI_NRPN_BITS                        0x40000000
 
-#define DSSI_NONE			-1
-#define DSSI_CONTROLLER_IS_SET(n)	(DSSI_NONE != (n))
+#define DSSI_NONE                        -1
+#define DSSI_CONTROLLER_IS_SET(n)        (DSSI_NONE != (n))
 
-#define DSSI_CC(n)			(DSSI_CC_BITS | (n))
-#define DSSI_IS_CC(n)			(DSSI_CC_BITS & (n))
-#define DSSI_CC_NUMBER(n)		((n) & 0x7f)
+#define DSSI_CC(n)                        (DSSI_CC_BITS | (n))
+#define DSSI_IS_CC(n)                        (DSSI_CC_BITS & (n))
+#define DSSI_CC_NUMBER(n)                ((n) & 0x7f)
 
-#define DSSI_NRPN(n)			(DSSI_NRPN_BITS | ((n) << 7))
-#define DSSI_IS_NRPN(n)			(DSSI_NRPN_BITS & (n))
-#define DSSI_NRPN_NUMBER(n)		(((n) >> 7) & 0x3fff)
+#define DSSI_NRPN(n)                        (DSSI_NRPN_BITS | ((n) << 7))
+#define DSSI_IS_NRPN(n)                        (DSSI_NRPN_BITS & (n))
+#define DSSI_NRPN_NUMBER(n)                (((n) >> 7) & 0x3fff)
 
 #ifdef __cplusplus
 }
--- a/plugin/plugins/SamplePlayer.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/plugins/SamplePlayer.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -29,6 +29,11 @@
 #include <QDir>
 #include <QFileInfo>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
 #include <sndfile.h>
 #include <samplerate.h>
 #include <iostream>
@@ -152,17 +157,17 @@
 SamplePlayer::instantiate(const LADSPA_Descriptor *, unsigned long rate)
 {
     if (!hostDescriptor || !hostDescriptor->request_non_rt_thread) {
-	SVDEBUG << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << endl;
-	return 0;
+        SVDEBUG << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << endl;
+        return 0;
     }
 
     SamplePlayer *player = new SamplePlayer(int(rate));
-	// std::cerr << "Instantiated sample player " << std::endl;
+        // std::cerr << "Instantiated sample player " << std::endl;
 
     if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) {
-	SVDEBUG << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << endl;
-	delete player;
-	return 0;
+        SVDEBUG << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << endl;
+        delete player;
+        return 0;
     }
 
     return player;
@@ -170,17 +175,17 @@
 
 void
 SamplePlayer::connectPort(LADSPA_Handle handle,
-			  unsigned long port, LADSPA_Data *location)
+                          unsigned long port, LADSPA_Data *location)
 {
     SamplePlayer *player = (SamplePlayer *)handle;
 
     float **ports[PortCount] = {
-	&player->m_output,
-	&player->m_retune,
-	&player->m_basePitch,
+        &player->m_output,
+        &player->m_retune,
+        &player->m_basePitch,
         &player->m_concertA,
-	&player->m_sustain,
-	&player->m_release
+        &player->m_sustain,
+        &player->m_release
     };
 
     *ports[port] = (float *)location;
@@ -195,9 +200,9 @@
     player->m_sampleNo = 0;
 
     for (size_t i = 0; i < Polyphony; ++i) {
-	player->m_ons[i] = -1;
-	player->m_offs[i] = -1;
-	player->m_velocities[i] = 0;
+        player->m_ons[i] = -1;
+        player->m_offs[i] = -1;
+        player->m_velocities[i] = 0;
     }
 }
 
@@ -226,7 +231,7 @@
 
         SamplePlayer *player = (SamplePlayer *)handle;
 
-	QMutexLocker locker(&player->m_mutex);
+        QMutexLocker locker(&player->m_mutex);
 
         if (QFileInfo(value).exists() &&
             QFileInfo(value).isDir()) {
@@ -256,17 +261,17 @@
     SamplePlayer *player = (SamplePlayer *)handle;
 
     if (!player->m_sampleSearchComplete) {
-	QMutexLocker locker(&player->m_mutex);
-	if (!player->m_sampleSearchComplete) {
-	    player->searchSamples();
-	}
+        QMutexLocker locker(&player->m_mutex);
+        if (!player->m_sampleSearchComplete) {
+            player->searchSamples();
+        }
     }
     if (program >= player->m_samples.size()) return 0;
 
     static DSSI_Program_Descriptor descriptor;
     static char name[60];
 
-    strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 60);
+    strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 59);
     name[59] = '\0';
 
     descriptor.Bank = 0;
@@ -278,8 +283,8 @@
 
 void
 SamplePlayer::selectProgram(LADSPA_Handle handle,
-			    unsigned long,
-			    unsigned long program)
+                            unsigned long,
+                            unsigned long program)
 {
     SamplePlayer *player = (SamplePlayer *)handle;
     player->m_pendingProgramChange = (int)program;
@@ -289,11 +294,11 @@
 SamplePlayer::getMidiController(LADSPA_Handle, unsigned long port)
 {
     int controllers[PortCount] = {
-	DSSI_NONE,
-	DSSI_CC(12),
-	DSSI_CC(13),
-	DSSI_CC(64),
-	DSSI_CC(72)
+        DSSI_NONE,
+        DSSI_CC(12),
+        DSSI_CC(13),
+        DSSI_CC(64),
+        DSSI_CC(72)
     };
 
     return controllers[port];
@@ -301,7 +306,7 @@
 
 void
 SamplePlayer::runSynth(LADSPA_Handle handle, unsigned long samples,
-		       snd_seq_event_t *events, unsigned long eventCount)
+                       snd_seq_event_t *events, unsigned long eventCount)
 {
     SamplePlayer *player = (SamplePlayer *)handle;
 
@@ -322,38 +327,38 @@
     if (player->m_pendingProgramChange >= 0) {
 
 #ifdef DEBUG_SAMPLE_PLAYER
-	SVDEBUG << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << endl;
+        SVDEBUG << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << endl;
 #endif
 
-	player->m_mutex.lock();
+        player->m_mutex.lock();
 
-	int program = player->m_pendingProgramChange;
-	player->m_pendingProgramChange = -1;
+        int program = player->m_pendingProgramChange;
+        player->m_pendingProgramChange = -1;
 
-	if (!player->m_sampleSearchComplete) {
-	    player->searchSamples();
-	}
-	
-	if (program < int(player->m_samples.size())) {
-	    QString path = player->m_samples[program].second;
-	    QString programName = player->m_samples[program].first;
-	    if (programName != player->m_program) {
-		player->m_program = programName;
-		player->m_mutex.unlock();
-		player->loadSampleData(path);
-	    } else {
-		player->m_mutex.unlock();
-	    }
-	}
+        if (!player->m_sampleSearchComplete) {
+            player->searchSamples();
+        }
+        
+        if (program < int(player->m_samples.size())) {
+            QString path = player->m_samples[program].second;
+            QString programName = player->m_samples[program].first;
+            if (programName != player->m_program) {
+                player->m_program = programName;
+                player->m_mutex.unlock();
+                player->loadSampleData(path);
+            } else {
+                player->m_mutex.unlock();
+            }
+        }
     }
 
     if (!player->m_sampleSearchComplete) {
 
-	QMutexLocker locker(&player->m_mutex);
+        QMutexLocker locker(&player->m_mutex);
 
-	if (!player->m_sampleSearchComplete) {
-	    player->searchSamples();
-	}
+        if (!player->m_sampleSearchComplete) {
+            player->searchSamples();
+        }
     }
 }
 
@@ -366,7 +371,7 @@
 
 #ifdef DEBUG_SAMPLE_PLAYER
     SVDEBUG << "SamplePlayer::searchSamples: Directory is \""
-	      << m_sampleDir << "\"" << endl;
+              << m_sampleDir << "\"" << endl;
 #endif
 
     QDir dir(m_sampleDir, "*.wav");
@@ -395,12 +400,16 @@
     size_t i;
 
     info.format = 0;
+#ifdef Q_OS_WIN
+    file = sf_wchar_open((LPCWSTR)path.utf16(), SFM_READ, &info);
+#else
     file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
+#endif
     if (!file) {
-	cerr << "SamplePlayer::loadSampleData: Failed to open file "
-		  << path << ": "
-		  << sf_strerror(file) << endl;
-	return;
+        cerr << "SamplePlayer::loadSampleData: Failed to open file "
+                  << path << ": "
+                  << sf_strerror(file) << endl;
+        return;
     }
     
     samples = info.frames;
@@ -413,47 +422,47 @@
     tmpResamples = 0;
 
     if (info.samplerate != m_sampleRate) {
-	
-	double ratio = (double)m_sampleRate / (double)info.samplerate;
-	size_t target = (size_t)(double(info.frames) * ratio);
-	SRC_DATA data;
+        
+        double ratio = (double)m_sampleRate / (double)info.samplerate;
+        size_t target = (size_t)(double(info.frames) * ratio);
+        SRC_DATA data;
 
-	tmpResamples = (float *)malloc(target * info.channels * sizeof(float));
-	if (!tmpResamples) {
-	    free(tmpFrames);
-	    return;
-	}
+        tmpResamples = (float *)malloc(target * info.channels * sizeof(float));
+        if (!tmpResamples) {
+            free(tmpFrames);
+            return;
+        }
 
-	memset(tmpResamples, 0, target * info.channels * sizeof(float));
+        memset(tmpResamples, 0, target * info.channels * sizeof(float));
 
-	data.data_in = tmpFrames;
-	data.data_out = tmpResamples;
-	data.input_frames = info.frames;
-	data.output_frames = target;
-	data.src_ratio = ratio;
+        data.data_in = tmpFrames;
+        data.data_out = tmpResamples;
+        data.input_frames = info.frames;
+        data.output_frames = target;
+        data.src_ratio = ratio;
 
-	if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) {
-	    free(tmpFrames);
-	    tmpFrames = tmpResamples;
-	    samples = target;
-	} else {
-	    free(tmpResamples);
-	}
+        if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) {
+            free(tmpFrames);
+            tmpFrames = tmpResamples;
+            samples = target;
+        } else {
+            free(tmpResamples);
+        }
     }
 
     /* add an extra sample for linear interpolation */
     tmpSamples = (float *)malloc((samples + 1) * sizeof(float));
     if (!tmpSamples) {
-	free(tmpFrames);
-	return;
+        free(tmpFrames);
+        return;
     }
 
     for (i = 0; i < samples; ++i) {
-	int j;
-	tmpSamples[i] = 0.0f;
-	for (j = 0; j < info.channels; ++j) {
-	    tmpSamples[i] += tmpFrames[i * info.channels + j];
-	}
+        int j;
+        tmpSamples[i] = 0.0f;
+        for (j = 0; j < info.channels; ++j) {
+            tmpSamples[i] += tmpFrames[i * info.channels + j];
+        }
     }
 
     free(tmpFrames);
@@ -468,9 +477,9 @@
     m_sampleCount = samples;
 
     for (i = 0; i < Polyphony; ++i) {
-	m_ons[i] = -1;
-	m_offs[i] = -1;
-	m_velocities[i] = 0;
+        m_ons[i] = -1;
+        m_offs[i] = -1;
+        m_velocities[i] = 0;
     }
 
     if (tmpOld) free(tmpOld);
@@ -480,8 +489,8 @@
 
 void
 SamplePlayer::runImpl(unsigned long sampleCount,
-		      snd_seq_event_t *events,
-		      unsigned long eventCount)
+                      snd_seq_event_t *events,
+                      unsigned long eventCount)
 {
     unsigned long pos;
     unsigned long count;
@@ -493,67 +502,67 @@
     if (!m_mutex.tryLock()) return;
 
     if (!m_sampleData || !m_sampleCount) {
-	m_sampleNo += sampleCount;
-	m_mutex.unlock();
-	return;
+        m_sampleNo += sampleCount;
+        m_mutex.unlock();
+        return;
     }
 
     for (pos = 0, event_pos = 0; pos < sampleCount; ) {
 
-	while (event_pos < eventCount
-	       && pos >= events[event_pos].time.tick) {
+        while (event_pos < eventCount
+               && pos >= events[event_pos].time.tick) {
 
-	    if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) {
+            if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) {
 #ifdef DEBUG_SAMPLE_PLAYER
                 cerr << "SamplePlayer: found NOTEON at time " 
                           << events[event_pos].time.tick << endl;
 #endif
-		snd_seq_ev_note_t n = events[event_pos].data.note;
-		if (n.velocity > 0) {
-		    m_ons[n.note] =
-			m_sampleNo + events[event_pos].time.tick;
-		    m_offs[n.note] = -1;
-		    m_velocities[n.note] = n.velocity;
-		} else {
-		    if (!m_sustain || (*m_sustain < 0.001)) {
-			m_offs[n.note] = 
-			    m_sampleNo + events[event_pos].time.tick;
-		    }
-		}
-	    } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF &&
-		       (!m_sustain || (*m_sustain < 0.001))) {
+                snd_seq_ev_note_t n = events[event_pos].data.note;
+                if (n.velocity > 0) {
+                    m_ons[n.note] =
+                        m_sampleNo + events[event_pos].time.tick;
+                    m_offs[n.note] = -1;
+                    m_velocities[n.note] = n.velocity;
+                } else {
+                    if (!m_sustain || (*m_sustain < 0.001)) {
+                        m_offs[n.note] = 
+                            m_sampleNo + events[event_pos].time.tick;
+                    }
+                }
+            } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF &&
+                       (!m_sustain || (*m_sustain < 0.001))) {
 #ifdef DEBUG_SAMPLE_PLAYER
                 cerr << "SamplePlayer: found NOTEOFF at time " 
                           << events[event_pos].time.tick << endl;
 #endif
-		snd_seq_ev_note_t n = events[event_pos].data.note;
-		m_offs[n.note] = 
-		    m_sampleNo + events[event_pos].time.tick;
-	    }
+                snd_seq_ev_note_t n = events[event_pos].data.note;
+                m_offs[n.note] = 
+                    m_sampleNo + events[event_pos].time.tick;
+            }
 
-	    ++event_pos;
-	}
+            ++event_pos;
+        }
 
-	count = sampleCount - pos;
-	if (event_pos < eventCount &&
-	    events[event_pos].time.tick < sampleCount) {
-	    count = events[event_pos].time.tick - pos;
-	}
+        count = sampleCount - pos;
+        if (event_pos < eventCount &&
+            events[event_pos].time.tick < sampleCount) {
+            count = events[event_pos].time.tick - pos;
+        }
 
         int notecount = 0;
 
-	for (i = 0; i < Polyphony; ++i) {
-	    if (m_ons[i] >= 0) {
+        for (i = 0; i < Polyphony; ++i) {
+            if (m_ons[i] >= 0) {
                 ++notecount;
-		addSample(i, pos, count);
-	    }
-	}
+                addSample(i, pos, count);
+            }
+        }
 
 #ifdef DEBUG_SAMPLE_PLAYER
         cerr << "SamplePlayer: have " << notecount << " note(s) sounding currently" << endl;
 #endif
 
-	pos += count;
+        pos += count;
     }
 
     m_sampleNo += sampleCount;
@@ -571,9 +580,9 @@
         if (m_concertA) {
             ratio *= *m_concertA / 440.f;
         }
-	if (m_basePitch && float(n) != *m_basePitch) {
-	    ratio *= powf(1.059463094f, float(n) - *m_basePitch);
-	}
+        if (m_basePitch && float(n) != *m_basePitch) {
+            ratio *= powf(1.059463094f, float(n) - *m_basePitch);
+        }
     }
 
     if (long(pos + m_sampleNo) < m_ons[n]) return;
@@ -581,49 +590,49 @@
     gain = (float)m_velocities[n] / 127.0f;
 
     for (i = 0, s = pos + m_sampleNo - m_ons[n];
-	 i < count;
-	 ++i, ++s) {
+         i < count;
+         ++i, ++s) {
 
-	float         lgain = gain;
-	float         rs = float(s) * ratio;
-	unsigned long rsi = lrintf(floorf(rs));
+        float         lgain = gain;
+        float         rs = float(s) * ratio;
+        unsigned long rsi = lrintf(floorf(rs));
 
-	if (rsi >= m_sampleCount) {
+        if (rsi >= m_sampleCount) {
 #ifdef DEBUG_SAMPLE_PLAYER
             cerr << "Note " << n << " has run out of samples (were " << m_sampleCount << " available at ratio " << ratio << "), ending" << endl;
 #endif
-	    m_ons[n] = -1;
-	    break;
-	}
+            m_ons[n] = -1;
+            break;
+        }
 
-	if (m_offs[n] >= 0 &&
-	    long(pos + i + m_sampleNo) > m_offs[n]) {
+        if (m_offs[n] >= 0 &&
+            long(pos + i + m_sampleNo) > m_offs[n]) {
 
-	    unsigned long dist =
-		pos + i + m_sampleNo - m_offs[n];
+            unsigned long dist =
+                pos + i + m_sampleNo - m_offs[n];
 
-	    unsigned long releaseFrames = 200;
-	    if (m_release) {
-		releaseFrames = long(*m_release * float(m_sampleRate) + 0.0001f);
-	    }
+            unsigned long releaseFrames = 200;
+            if (m_release) {
+                releaseFrames = long(*m_release * float(m_sampleRate) + 0.0001f);
+            }
 
-	    if (dist > releaseFrames) {
+            if (dist > releaseFrames) {
 #ifdef DEBUG_SAMPLE_PLAYER
                 cerr << "Note " << n << " has expired its release time (" << releaseFrames << " frames), ending" << endl;
 #endif
-		m_ons[n] = -1;
-		break;
-	    } else {
-		lgain = lgain * (float)(releaseFrames - dist) /
-		    (float)releaseFrames;
-	    }
-	}
-	
-	float sample = m_sampleData[rsi] +
-	    ((m_sampleData[rsi + 1] -
-	      m_sampleData[rsi]) *
-	     (rs - (float)rsi));
+                m_ons[n] = -1;
+                break;
+            } else {
+                lgain = lgain * (float)(releaseFrames - dist) /
+                    (float)releaseFrames;
+            }
+        }
+        
+        float sample = m_sampleData[rsi] +
+            ((m_sampleData[rsi + 1] -
+              m_sampleData[rsi]) *
+             (rs - (float)rsi));
 
-	m_output[pos + i] += lgain * sample;
+        m_output[pos + i] += lgain * sample;
     }
 }
--- a/plugin/plugins/SamplePlayer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/plugin/plugins/SamplePlayer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -37,17 +37,17 @@
     ~SamplePlayer();
 
     enum {
-	OutputPort    = 0,
-	RetunePort    = 1,
-	BasePitchPort = 2,
+        OutputPort    = 0,
+        RetunePort    = 1,
+        BasePitchPort = 2,
         ConcertAPort  = 3,
-	SustainPort   = 4,
-	ReleasePort   = 5,
-	PortCount     = 6
+        SustainPort   = 4,
+        ReleasePort   = 5,
+        PortCount     = 6
     };
 
     enum {
-	Polyphony = 128
+        Polyphony = 128
     };
 
     static const char *const portNames[PortCount];
@@ -69,7 +69,7 @@
     static void selectProgram(LADSPA_Handle, unsigned long, unsigned long);
     static int getMidiController(LADSPA_Handle, unsigned long);
     static void runSynth(LADSPA_Handle, unsigned long,
-			 snd_seq_event_t *, unsigned long);
+                         snd_seq_event_t *, unsigned long);
     static void receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor);
     static void workThreadCallback(LADSPA_Handle);
 
--- a/rdf/RDFFeatureWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/rdf/RDFFeatureWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -146,14 +146,14 @@
         m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId);
 
         if (m_rdfDescriptions[pluginId].haveDescription()) {
-            cerr << "NOTE: Have RDF description for plugin ID \""
+            SVCERR << "NOTE: Have RDF description for plugin ID \""
                  << pluginId << "\"" << endl;
         } else {
-            cerr << "NOTE: No RDF description for plugin ID \""
+            SVCERR << "NOTE: No RDF description for plugin ID \""
                  << pluginId << "\"" << endl;
             if (!m_network) {
-                cerr << "      Consider using the --rdf-network option to retrieve plugin descriptions"  << endl;
-                cerr << "      from the network where possible." << endl;
+                SVCERR << "      Consider using the --rdf-network option to retrieve plugin descriptions"  << endl;
+                SVCERR << "      from the network where possible." << endl;
             }
         }
     }
@@ -194,7 +194,7 @@
     QString timelineURI = m_trackTimelineURIs[trackId];
     
     if (timelineURI == "") {
-        cerr << "RDFFeatureWriter: INTERNAL ERROR: writing features without having established a timeline URI!" << endl;
+        SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing features without having established a timeline URI!" << endl;
         exit(1);
     }
 
@@ -211,7 +211,7 @@
         QString signalURI = m_trackSignalURIs[trackId];
 
         if (signalURI == "") {
-            cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having established a signal URI!" << endl;
+            SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having established a signal URI!" << endl;
             exit(1);
         }
 
@@ -229,7 +229,7 @@
         QString signalURI = m_trackSignalURIs[trackId];
 
         if (signalURI == "") {
-            cerr << "RDFFeatureWriter: INTERNAL ERROR: writing track-level features without having established a signal URI!" << endl;
+            SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing track-level features without having established a signal URI!" << endl;
             exit(1);
         }
 
@@ -702,19 +702,19 @@
 
             sampleRate = transform.getSampleRate();
             if (sampleRate == 0.f) {
-                cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the sample rate properly!" << endl;
+                SVCERR << "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;
+                SVCERR << "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;
+                SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the block size properly!" << endl;
                 return;
             }
         }
--- a/rdf/RDFImporter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/rdf/RDFImporter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -818,7 +818,7 @@
         store = BasicStore::load(QUrl(url));
         Triple t = store->matchOnce(Triple());
         if (t != Triple()) haveRDF = true;
-    } catch (std::exception &e) {
+    } catch (std::exception &) {
         // nothing; haveRDF will be false so the next bit catches it
     }
 
--- a/system/System.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/system/System.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -1,16 +1,16 @@
 /* -*- 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.
+  Sonic Visualiser
+  An audio file viewer and annotation editor.
+  Centre for Digital Music, Queen Mary, University of London.
+  This file copyright 2006-2018 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.
+  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 "System.h"
@@ -39,16 +39,16 @@
 
 #ifdef __APPLE__
 extern "C" {
-void *
-rpl_realloc (void *p, size_t n)
-{
-    p = realloc(p, n);
-    if (p == 0 && n == 0)
+    void *
+    rpl_realloc (void *p, size_t n)
     {
-    p = malloc(0);
+        p = realloc(p, n);
+        if (p == 0 && n == 0)
+        {
+            p = malloc(0);
+        }
+        return p;
     }
-    return p;
-}
 }
 #endif
 
@@ -57,24 +57,24 @@
 extern "C" {
 
 #ifdef _MSC_VER
-void usleep(unsigned long usec)
-{
-    ::Sleep(usec / 1000);
-}
+    void usleep(unsigned long usec)
+    {
+        ::Sleep(usec / 1000);
+    }
 #endif
 
-int gettimeofday(struct timeval *tv, void *tz)
-{
-    union { 
-	long long ns100;  
-	FILETIME ft; 
-    } now; 
+    int gettimeofday(struct timeval *tv, void * /* tz */)
+    {
+        union { 
+            long long ns100;  
+            FILETIME ft; 
+        } now; 
     
-    ::GetSystemTimeAsFileTime(&now.ft); 
-    tv->tv_usec = (long)((now.ns100 / 10LL) % 1000000LL); 
-    tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); 
-    return 0;
-}
+        ::GetSystemTimeAsFileTime(&now.ft); 
+        tv->tv_usec = (long)((now.ns100 / 10LL) % 1000000LL); 
+        tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); 
+        return 0;
+    }
 
 }
 
@@ -153,10 +153,10 @@
     if (exFound) {
 
         lMEMORYSTATUSEX lms;
-	lms.dwLength = sizeof(lms);
-	if (!ex(&lms)) {
+        lms.dwLength = sizeof(lms);
+        if (!ex(&lms)) {
             cerr << "WARNING: GlobalMemoryStatusEx failed: error code "
-                      << GetLastError() << endl;
+                 << GetLastError() << endl;
             return;
         }
         wavail = lms.ullAvailPhys;
@@ -167,9 +167,9 @@
         /* Fall back to GlobalMemoryStatus which is always available.
            but returns wrong results for physical memory > 4GB  */
 
-	MEMORYSTATUS ms;
-	GlobalMemoryStatus(&ms);
-	wavail = ms.dwAvailPhys;
+        MEMORYSTATUS ms;
+        GlobalMemoryStatus(&ms);
+        wavail = ms.dwAvailPhys;
         wtotal = ms.dwTotalPhys;
     }
 
@@ -211,7 +211,10 @@
 
     char buf[256];
     while (!feof(meminfo)) {
-        fgets(buf, 256, meminfo);
+        if (!fgets(buf, 256, meminfo)) {
+            fclose(meminfo);
+            return;
+        }
         bool isMemFree = (strncmp(buf, "MemFree:", 8) == 0);
         bool isMemTotal = (!isMemFree && (strncmp(buf, "MemTotal:", 9) == 0));
         if (isMemFree || isMemTotal) {
@@ -249,13 +252,13 @@
 #ifdef _WIN32
     ULARGE_INTEGER available, total, totalFree;
     if (GetDiskFreeSpaceExA(path, &available, &total, &totalFree)) {
-	  __int64 a = available.QuadPart;
+        __int64 a = available.QuadPart;
         a /= 1048576;
         if (a > INT_MAX) a = INT_MAX;
         return ssize_t(a);
     } else {
         cerr << "WARNING: GetDiskFreeSpaceEx failed: error code "
-                  << GetLastError() << endl;
+             << GetLastError() << endl;
         return -1;
     }
 #else
@@ -286,7 +289,7 @@
 #endif
 }
 #else /* !_WIN32 */
-#if !defined(__APPLE__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0))
+#if !defined(__APPLE__) && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0))
 void
 SystemMemoryBarrier()
 {
@@ -325,4 +328,109 @@
 double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
 float princargf(float a) { return float(princarg(a)); }
 
+bool
+getEnvUtf8(std::string variable, std::string &value)
+{
+    value = "";
+    
+#ifdef _WIN32
+    int wvarlen = MultiByteToWideChar(CP_UTF8, 0,
+                                      variable.c_str(), int(variable.length()),
+                                      0, 0);
+    if (wvarlen < 0) {
+        SVCERR << "WARNING: Unable to convert environment variable name "
+               << variable << " to wide characters" << endl;
+        return false;
+    }
+    
+    wchar_t *wvarbuf = new wchar_t[wvarlen + 1];
+    (void)MultiByteToWideChar(CP_UTF8, 0,
+                              variable.c_str(), int(variable.length()),
+                              wvarbuf, wvarlen);
+    wvarbuf[wvarlen] = L'\0';
+    
+    wchar_t *wvalue = _wgetenv(wvarbuf);
 
+    delete[] wvarbuf;
+
+    if (!wvalue) {
+        return false;
+    }
+
+    int wvallen = int(wcslen(wvalue));
+    int vallen = WideCharToMultiByte(CP_UTF8, 0,
+                                     wvalue, wvallen,
+                                     0, 0, 0, 0);
+    if (vallen < 0) {
+        SVCERR << "WARNING: Unable to convert environment value to UTF-8"
+               << endl;
+        return false;
+    }
+
+    char *val = new char[vallen + 1];
+    (void)WideCharToMultiByte(CP_UTF8, 0,
+                              wvalue, wvallen,
+                              val, vallen, 0, 0);
+    val[vallen] = '\0';
+
+    value = val;
+
+    delete[] val;
+    return true;
+
+#else
+
+    char *val = getenv(variable.c_str());
+    if (!val) {
+        return false;
+    }
+
+    value = val;
+    return true;
+    
+#endif
+}
+
+bool
+putEnvUtf8(std::string variable, std::string value)
+{
+#ifdef _WIN32
+    std::string entry = variable + "=" + value;
+    
+    int wentlen = MultiByteToWideChar(CP_UTF8, 0,
+                                      entry.c_str(), int(entry.length()),
+                                      0, 0);
+    if (wentlen < 0) {
+        SVCERR << "WARNING: Unable to convert environment entry to "
+               << "wide characters" << endl;
+        return false;
+    }
+    
+    wchar_t *wentbuf = new wchar_t[wentlen + 1];
+    (void)MultiByteToWideChar(CP_UTF8, 0,
+                              entry.c_str(), int(entry.length()),
+                              wentbuf, wentlen);
+    wentbuf[wentlen] = L'\0';
+
+    int rv = _wputenv(wentbuf);
+
+    delete[] wentbuf;
+
+    if (rv != 0) {
+        SVCERR << "WARNING: Failed to set environment entry" << endl;
+        return false;
+    }
+    return true;
+
+#else
+
+    int rv = setenv(variable.c_str(), value.c_str(), 1);
+    if (rv != 0) {
+        SVCERR << "WARNING: Failed to set environment entry" << endl;
+        return false;
+    }
+    return true;
+    
+#endif
+}
+
--- a/system/System.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/system/System.h	Mon Sep 17 13:51:14 2018 +0100
@@ -4,7 +4,7 @@
     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 file copyright 2006-2018 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
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _SYSTEM_H_
-#define _SYSTEM_H_
+#ifndef SV_SYSTEM_H
+#define SV_SYSTEM_H
 
 #include "base/Debug.h"
 
@@ -154,7 +154,8 @@
 extern ProcessStatus GetProcessStatus(int pid);
 
 // Return a vague approximation to the number of free megabytes of real memory.
-// Return -1 if unknown. (Hence signed args)
+// Return -1 if unknown. (Hence signed args.) Note that this could be more than
+// is actually addressable, e.g. for a 32-bit process on a 64-bit system.
 extern void GetRealMemoryMBAvailable(ssize_t &available, ssize_t &total);
 
 // Return a vague approximation to the number of free megabytes of
@@ -181,6 +182,18 @@
 #define powf pow
 #endif
 
+/** Return the value of the given environment variable by reference.
+    Return true if successfully retrieved, false if unset or on error.
+    Both the variable name and the returned value are UTF-8 encoded.
+*/
+extern bool getEnvUtf8(std::string variable, std::string &value);
+
+/** Set the value of the given environment variable.
+    Return true if successfully set, false on error.
+    Both the variable name and the value must be UTF-8 encoded.
+*/
+extern bool putEnvUtf8(std::string variable, std::string value);
+
 #endif /* ! _SYSTEM_H_ */
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system/test/TestEnv.h	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,107 @@
+/* -*- 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_ENV_H
+#define TEST_ENV_H
+
+#include "../System.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+const std::string utf8_name_sprkt = "\343\202\271\343\203\235\343\203\274\343\202\257\343\201\256\345\257\272\351\231\242";
+
+class TestEnv : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void getAbsent()
+    {
+        string value = "blather";
+        bool rv = getEnvUtf8("nonexistent_environment_variable_I_sincerely_hope_including_a_missspellling_just_to_be_sure",
+                             value);
+        QCOMPARE(rv, false);
+        QCOMPARE(value, string());
+    }
+
+    void getExpected()
+    {
+        string value;
+        bool rv = getEnvUtf8("PATH", value);
+        QCOMPARE(rv, true);
+        QVERIFY(value != "");
+        QVERIFY(value.size() > 5); // Not quite but nearly certain,
+                                   // and weeds out an unfortunate
+                                   // case where we accidentally
+                                   // returned the variable's name
+                                   // instead of its value!
+    }
+    
+    void roundTripAsciiAscii()
+    {
+        bool rv = false;
+        rv = putEnvUtf8("SV_CORE_TEST_SYSTEM_RT_A_A", "EXPECTED_VALUE");
+        QCOMPARE(rv, true);
+        string value;
+        rv = getEnvUtf8("SV_CORE_TEST_SYSTEM_RT_A_A", value);
+        QCOMPARE(rv, true);
+        QCOMPARE(value, string("EXPECTED_VALUE"));
+    }
+    
+    void roundTripAsciiUtf8()
+    {
+        bool rv = false;
+        rv = putEnvUtf8("SV_CORE_TEST_SYSTEM_RT_A_U", utf8_name_sprkt);
+        QCOMPARE(rv, true);
+        string value;
+        rv = getEnvUtf8("SV_CORE_TEST_SYSTEM_RT_A_U", value);
+        QCOMPARE(rv, true);
+        QCOMPARE(value, utf8_name_sprkt);
+    }
+    
+    void roundTripUtf8Ascii()
+    {
+        bool rv = false;
+        rv = putEnvUtf8("SV_CORE_TEST_SYSTEM_RT_\351\207\215\345\272\206_A", "EXPECTED_VALUE");
+        QCOMPARE(rv, true);
+        string value;
+        rv = getEnvUtf8("SV_CORE_TEST_SYSTEM_RT_\351\207\215\345\272\206_A", value);
+        QCOMPARE(rv, true);
+        QCOMPARE(value, string("EXPECTED_VALUE"));
+    }
+
+    void roundTripUtf8Utf8()
+    {
+        bool rv = false;
+        rv = putEnvUtf8("SV_CORE_TEST_SYSTEM_RT_\351\207\215\345\272\206_A", utf8_name_sprkt);
+        QCOMPARE(rv, true);
+        string value;
+        rv = getEnvUtf8("SV_CORE_TEST_SYSTEM_RT_\351\207\215\345\272\206_A", value);
+        QCOMPARE(rv, true);
+        QCOMPARE(value, utf8_name_sprkt);
+    }
+};
+
+#endif
+
+    
+
+    
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system/test/files.pri	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,5 @@
+TEST_HEADERS = \
+	     TestEnv.h
+	     
+TEST_SOURCES += \
+	     svcore-system-test.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system/test/svcore-system-test.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -0,0 +1,41 @@
+/* -*- 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 "TestEnv.h"
+
+#include <QtTest>
+
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+    int good = 0, bad = 0;
+
+    QCoreApplication app(argc, argv);
+    app.setOrganizationName("sonic-visualiser");
+    app.setApplicationName("test-svcore-system");
+
+    {
+        TestEnv t;
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    if (bad > 0) {
+        SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+        return 1;
+    } else {
+        SVCERR << "All tests passed" << endl;
+        return 0;
+    }
+}
--- a/transform/CSVFeatureWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/CSVFeatureWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -117,8 +117,8 @@
         } else if (i->first == "digits") {
             int digits = atoi(i->second.c_str());
             if (digits <= 0 || digits > 100) {
-                cerr << "CSVFeatureWriter: ERROR: Invalid or out-of-range value for number of significant digits: " << i->second << endl;
-                cerr << "CSVFeatureWriter: NOTE: Continuing with default settings" << endl;
+                SVCERR << "CSVFeatureWriter: ERROR: Invalid or out-of-range value for number of significant digits: " << i->second << endl;
+                SVCERR << "CSVFeatureWriter: NOTE: Continuing with default settings" << endl;
             } else {
                 m_digits = digits;
             }
--- a/transform/FeatureExtractionModelTransformer.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -85,6 +85,7 @@
     for (int j = 1; j < (int)m_transforms.size(); ++j) {
         if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) {
             m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output");
+            SVCERR << m_message << endl;
             return false;
         }
     }
@@ -98,12 +99,14 @@
 
     if (!factory) {
         m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
-	return false;
+        SVCERR << m_message << endl;
+        return false;
     }
 
     DenseTimeValueModel *input = getConformingInput();
     if (!input) {
         m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
+        SVCERR << m_message << endl;
         return false;
     }
 
@@ -113,18 +116,19 @@
     m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
     if (!m_plugin) {
         m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
-	return false;
+        SVCERR << m_message << endl;
+        return false;
     }
 
     TransformFactory::getInstance()->makeContextConsistentWithPlugin
         (primaryTransform, m_plugin);
-
+    
     TransformFactory::getInstance()->setPluginParameters
         (primaryTransform, m_plugin);
-
+    
     int channelCount = input->getChannelCount();
     if ((int)m_plugin->getMaxChannelCount() < channelCount) {
-	channelCount = 1;
+        channelCount = 1;
     }
     if ((int)m_plugin->getMinChannelCount() > channelCount) {
         m_message = tr("Cannot provide enough channels to feature extraction plugin \"%1\" (plugin min is %2, max %3; input model has %4)")
@@ -132,59 +136,60 @@
             .arg(m_plugin->getMinChannelCount())
             .arg(m_plugin->getMaxChannelCount())
             .arg(input->getChannelCount());
-	return false;
+        SVCERR << m_message << endl;
+        return false;
     }
 
+    int step = primaryTransform.getStepSize();
+    int block = primaryTransform.getBlockSize();
+    
     SVDEBUG << "Initialising feature extraction plugin with channels = "
-            << channelCount << ", step = " << primaryTransform.getStepSize()
-            << ", block = " << primaryTransform.getBlockSize() << endl;
+            << channelCount << ", step = " << step
+            << ", block = " << block << endl;
 
-    if (!m_plugin->initialise(channelCount,
-                              primaryTransform.getStepSize(),
-                              primaryTransform.getBlockSize())) {
+    if (!m_plugin->initialise(channelCount, step, block)) {
+
+        int preferredStep = int(m_plugin->getPreferredStepSize());
+        int preferredBlock = int(m_plugin->getPreferredBlockSize());
         
-        int pstep = primaryTransform.getStepSize();
-        int pblock = primaryTransform.getBlockSize();
+        if (step != preferredStep || block != preferredBlock) {
 
-///!!! hang on, this isn't right -- we're modifying a copy
-        primaryTransform.setStepSize(0);
-        primaryTransform.setBlockSize(0);
-        TransformFactory::getInstance()->makeContextConsistentWithPlugin
-            (primaryTransform, m_plugin);
-
-        if (primaryTransform.getStepSize() != pstep ||
-            primaryTransform.getBlockSize() != pblock) {
-
-            SVDEBUG << "Initialisation failed, trying again with default step = "
-                    << primaryTransform.getStepSize()
-                    << ", block = " << primaryTransform.getBlockSize() << endl;
+            SVDEBUG << "Initialisation failed, trying again with preferred step = "
+                    << preferredStep << ", block = " << preferredBlock << endl;
             
-            if (!m_plugin->initialise(channelCount,
-                                      primaryTransform.getStepSize(),
-                                      primaryTransform.getBlockSize())) {
+            if (!m_plugin->initialise(channelCount, preferredStep, preferredBlock)) {
 
                 SVDEBUG << "Initialisation failed again" << endl;
                 
                 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
+                SVCERR << m_message << endl;
                 return false;
 
             } else {
                 
                 SVDEBUG << "Initialisation succeeded this time" << endl;
+
+                // Set these values into the primary transform in the list
+                m_transforms[0].setStepSize(preferredStep);
+                m_transforms[0].setBlockSize(preferredBlock);
                 
                 m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead")
                     .arg(pluginId)
-                    .arg(pstep)
-                    .arg(pblock)
-                    .arg(primaryTransform.getStepSize())
-                    .arg(primaryTransform.getBlockSize());
+                    .arg(step)
+                    .arg(block)
+                    .arg(preferredStep)
+                    .arg(preferredBlock);
+                SVCERR << m_message << endl;
             }
 
         } else {
 
-            SVDEBUG << "Initialisation failed" << endl;
+            SVDEBUG << "Initialisation failed (with step = " << step
+                    << " and block = " << block
+                    << ", both matching the plugin's preference)" << endl;
                 
             m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
+            SVCERR << m_message << endl;
             return false;
         }
     } else {
@@ -203,6 +208,7 @@
             } else {
                 m_message = vm;
             }
+            SVCERR << m_message << endl;
         }
     }
 
@@ -210,7 +216,8 @@
 
     if (outputs.empty()) {
         m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId);
-	return false;
+        SVCERR << m_message << endl;
+        return false;
     }
 
     for (int j = 0; j < (int)m_transforms.size(); ++j) {
@@ -230,6 +237,7 @@
             m_message = tr("Plugin \"%1\" has no output named \"%2\"")
                 .arg(pluginId)
                 .arg(m_transforms[j].getOutput());
+            SVCERR << m_message << endl;
             return false;
         }
     }
@@ -251,8 +259,18 @@
 {
     SVDEBUG << "FeatureExtractionModelTransformer: deleting plugin for transform in thread "
             << QThread::currentThreadId() << endl;
-    
-    delete m_plugin;
+
+    try {
+        delete m_plugin;
+    } catch (const std::exception &e) {
+        // A destructor shouldn't throw an exception. But at one point
+        // (now fixed) our plugin stub destructor could have
+        // accidentally done so, so just in case:
+        SVCERR << "FeatureExtractionModelTransformer: caught exception while deleting plugin: " << e.what() << endl;
+        m_message = e.what();
+    }
+    m_plugin = 0;
+        
     for (int j = 0; j < (int)m_descriptors.size(); ++j) {
         delete m_descriptors[j];
     }
@@ -272,17 +290,17 @@
     bool haveBinCount = m_descriptors[n]->hasFixedBinCount;
 
     if (haveBinCount) {
-	binCount = (int)m_descriptors[n]->binCount;
+        binCount = (int)m_descriptors[n]->binCount;
     }
 
     m_needAdditionalModels[n] = false;
 
 //    cerr << "FeatureExtractionModelTransformer: output bin count "
-//	      << binCount << endl;
+//              << binCount << endl;
 
     if (binCount > 0 && m_descriptors[n]->hasKnownExtents) {
-	minValue = m_descriptors[n]->minValue;
-	maxValue = m_descriptors[n]->maxValue;
+        minValue = m_descriptors[n]->minValue;
+        maxValue = m_descriptors[n]->maxValue;
         haveExtents = true;
     }
 
@@ -312,14 +330,14 @@
     switch (m_descriptors[n]->sampleType) {
 
     case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
-	if (outputRate != 0.0) {
-	    modelResolution = int(round(modelRate / outputRate));
-	}
-	break;
+        if (outputRate != 0.0) {
+            modelResolution = int(round(modelRate / outputRate));
+        }
+        break;
 
     case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
-	modelResolution = m_transforms[n].getStepSize();
-	break;
+        modelResolution = m_transforms[n].getStepSize();
+        break;
 
     case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
         if (outputRate <= 0.0) {
@@ -329,7 +347,7 @@
             modelResolution = int(round(modelRate / outputRate));
 //            cerr << "modelRate = " << modelRate << ", descriptor rate = " << outputRate << ", modelResolution = " << modelResolution << endl;
         }
-	break;
+        break;
     }
 
     bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
@@ -498,13 +516,13 @@
              EditableDenseThreeDimensionalModel::BasicMultirateCompression,
              false);
 
-	if (!m_descriptors[n]->binNames.empty()) {
-	    std::vector<QString> names;
-	    for (int i = 0; i < (int)m_descriptors[n]->binNames.size(); ++i) {
-		names.push_back(m_descriptors[n]->binNames[i].c_str());
-	    }
-	    model->setBinNames(names);
-	}
+        if (!m_descriptors[n]->binNames.empty()) {
+            std::vector<QString> names;
+            for (int i = 0; i < (int)m_descriptors[n]->binNames.size(); ++i) {
+                names.push_back(m_descriptors[n]->binNames[i].c_str());
+            }
+            model->setBinNames(names);
+        }
         
         out = model;
 
@@ -522,8 +540,8 @@
 FeatureExtractionModelTransformer::awaitOutputModels()
 {
     m_outputMutex.lock();
-    while (!m_haveOutputs) {
-        m_outputsCondition.wait(&m_outputMutex);
+    while (!m_haveOutputs && !m_abandoned) {
+        m_outputsCondition.wait(&m_outputMutex, 500);
     }
     m_outputMutex.unlock();
 }
@@ -603,9 +621,9 @@
 //    SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: input model is " << getInputModel() << endl;
 
     DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+        dynamic_cast<DenseTimeValueModel *>(getInputModel());
     if (!dtvm) {
-	SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl;
+        SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl;
     }
     return dtvm;
 }
@@ -613,12 +631,27 @@
 void
 FeatureExtractionModelTransformer::run()
 {
-    initialise();
+    try {
+        if (!initialise()) {
+            abandon();
+            return;
+        }
+    } catch (const std::exception &e) {
+        abandon();
+        m_message = e.what();
+        return;
+    }
     
     DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
+    if (!input) {
+        abandon();
+        return;
+    }
 
-    if (m_outputs.empty()) return;
+    if (m_outputs.empty()) {
+        abandon();
+        return;
+    }
 
     Transform primaryTransform = m_transforms[0];
 
@@ -632,12 +665,12 @@
 
     int channelCount = input->getChannelCount();
     if ((int)m_plugin->getMaxChannelCount() < channelCount) {
-	channelCount = 1;
+        channelCount = 1;
     }
 
     float **buffers = new float*[channelCount];
     for (int ch = 0; ch < channelCount; ++ch) {
-	buffers[ch] = new float[primaryTransform.getBlockSize() + 2];
+        buffers[ch] = new float[primaryTransform.getBlockSize() + 2];
     }
 
     int stepSize = primaryTransform.getStepSize();
@@ -710,85 +743,93 @@
 
     QString error = "";
 
-    while (!m_abandoned) {
+    try {
+        while (!m_abandoned) {
 
-        if (frequencyDomain) {
-            if (blockFrame - int(blockSize)/2 >
-                contextStart + contextDuration) break;
-        } else {
-            if (blockFrame >= 
-                contextStart + contextDuration) break;
+            if (frequencyDomain) {
+                if (blockFrame - int(blockSize)/2 >
+                    contextStart + contextDuration) break;
+            } else {
+                if (blockFrame >= 
+                    contextStart + contextDuration) break;
+            }
+
+//        SVDEBUG << "FeatureExtractionModelTransformer::run: blockFrame "
+//                  << blockFrame << ", endFrame " << endFrame << ", blockSize "
+//                  << blockSize << endl;
+
+            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 = 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 != "") {
+                        SVCERR << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
+                        m_abandoned = true;
+                        m_message = error;
+                        break;
+                    }
+                }
+            } else {
+                getFrames(channelCount, blockFrame, blockSize, buffers);
+            }
+
+            if (m_abandoned) break;
+
+            Vamp::Plugin::FeatureSet features = m_plugin->process
+                (buffers, RealTime::frame2RealTime(blockFrame, sampleRate).toVampRealTime());
+
+            if (m_abandoned) break;
+
+            for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
+                    Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
+                    addFeature(j, blockFrame, feature);
+                }
+            }
+
+            if (blockFrame == contextStart || completion > prevCompletion) {
+                for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                    setCompletion(j, completion);
+                }
+                prevCompletion = completion;
+            }
+
+            blockFrame += stepSize;
+
         }
 
-//	SVDEBUG << "FeatureExtractionModelTransformer::run: blockFrame "
-//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
-//                  << blockSize << endl;
+        if (!m_abandoned) {
+            Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
 
-	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 = 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 != "") {
-                    SVDEBUG << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
-                    m_abandoned = true;
-                    m_message = error;
-                    break;
+            for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
+                    Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
+                    addFeature(j, blockFrame, feature);
                 }
             }
-        } else {
-            getFrames(channelCount, blockFrame, blockSize, buffers);
         }
-
-        if (m_abandoned) break;
-
-	Vamp::Plugin::FeatureSet features = m_plugin->process
-	    (buffers, RealTime::frame2RealTime(blockFrame, sampleRate).toVampRealTime());
-
-        if (m_abandoned) break;
-
-        for (int j = 0; j < (int)m_outputNos.size(); ++j) {
-            for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
-                Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
-                addFeature(j, blockFrame, feature);
-            }
-        }
-
-	if (blockFrame == contextStart || completion > prevCompletion) {
-            for (int j = 0; j < (int)m_outputNos.size(); ++j) {
-                setCompletion(j, completion);
-            }
-	    prevCompletion = completion;
-	}
-
-	blockFrame += stepSize;
-    }
-
-    if (!m_abandoned) {
-        Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
-
-        for (int j = 0; j < (int)m_outputNos.size(); ++j) {
-            for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
-                Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
-                addFeature(j, blockFrame, feature);
-            }
-        }
+    } catch (const std::exception &e) {
+        SVCERR << "FeatureExtractionModelTransformer::run: Exception caught: "
+               << e.what() << endl;
+        m_abandoned = true;
+        m_message = e.what();
     }
 
     for (int j = 0; j < (int)m_outputNos.size(); ++j) {
@@ -886,23 +927,23 @@
     sv_frame_t frame = blockFrame;
 
     if (m_descriptors[n]->sampleType ==
-	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+        Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
 
-	if (!feature.hasTimestamp) {
-	    SVDEBUG
-		<< "WARNING: FeatureExtractionModelTransformer::addFeature: "
-		<< "Feature has variable sample rate but no timestamp!"
-		<< endl;
-	    return;
-	} else {
-	    frame = RealTime::realTime2Frame(feature.timestamp, inputRate);
-	}
+        if (!feature.hasTimestamp) {
+            SVDEBUG
+                << "WARNING: FeatureExtractionModelTransformer::addFeature: "
+                << "Feature has variable sample rate but no timestamp!"
+                << endl;
+            return;
+        } else {
+            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) {
+               Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
 
         sv_samplerate_t rate = m_descriptors[n]->sampleRate;
         if (rate <= 0.0) {
@@ -943,16 +984,16 @@
 
         SparseOneDimensionalModel *model =
             getConformingOutput<SparseOneDimensionalModel>(n);
-	if (!model) return;
+        if (!model) return;
 
         model->addPoint(SparseOneDimensionalModel::Point
                        (frame, feature.label.c_str()));
-	
+        
     } else if (isOutput<SparseTimeValueModel>(n)) {
 
-	SparseTimeValueModel *model =
+        SparseTimeValueModel *model =
             getConformingOutput<SparseTimeValueModel>(n);
-	if (!model) return;
+        if (!model) return;
 
         for (int i = 0; i < (int)feature.values.size(); ++i) {
 
@@ -1010,7 +1051,7 @@
                                                   duration,
                                                   velocity / 127.f,
                                                   feature.label.c_str()));
-			// GF: end -- added for flexi note model
+                        // GF: end -- added for flexi note model
         } else  if (isOutput<NoteModel>(n)) {
 
             float velocity = 100;
@@ -1055,14 +1096,14 @@
                                                    feature.label.c_str()));
             }
         }
-	
+        
     } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
-	
-	DenseThreeDimensionalModel::Column values = feature.values;
-	
-	EditableDenseThreeDimensionalModel *model =
+        
+        DenseThreeDimensionalModel::Column values = feature.values;
+        
+        EditableDenseThreeDimensionalModel *model =
             getConformingOutput<EditableDenseThreeDimensionalModel>(n);
-	if (!model) return;
+        if (!model) return;
 
 //        cerr << "(note: model resolution = " << model->getResolution() << ")"
 //             << endl;
@@ -1086,48 +1127,48 @@
 
     if (isOutput<SparseOneDimensionalModel>(n)) {
 
-	SparseOneDimensionalModel *model =
+        SparseOneDimensionalModel *model =
             getConformingOutput<SparseOneDimensionalModel>(n);
-	if (!model) return;
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true);
+        model->setCompletion(completion, true);
 
     } else if (isOutput<SparseTimeValueModel>(n)) {
 
-	SparseTimeValueModel *model =
+        SparseTimeValueModel *model =
             getConformingOutput<SparseTimeValueModel>(n);
-	if (!model) return;
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true);
+        model->setCompletion(completion, true);
 
     } else if (isOutput<NoteModel>(n)) {
 
-	NoteModel *model = getConformingOutput<NoteModel>(n);
-	if (!model) return;
+        NoteModel *model = getConformingOutput<NoteModel>(n);
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true);
-	
+        model->setCompletion(completion, true);
+        
     } else if (isOutput<FlexiNoteModel>(n)) {
 
-	FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
-	if (!model) return;
+        FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true);
+        model->setCompletion(completion, true);
 
     } else if (isOutput<RegionModel>(n)) {
 
-	RegionModel *model = getConformingOutput<RegionModel>(n);
-	if (!model) return;
+        RegionModel *model = getConformingOutput<RegionModel>(n);
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true);
+        model->setCompletion(completion, true);
 
     } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
 
-	EditableDenseThreeDimensionalModel *model =
+        EditableDenseThreeDimensionalModel *model =
             getConformingOutput<EditableDenseThreeDimensionalModel>(n);
-	if (!model) return;
+        if (!model) return;
         if (model->isAbandoning()) abandon();
-	model->setCompletion(completion, true); //!!!m_context.updates);
+        model->setCompletion(completion, true); //!!!m_context.updates);
     }
 }
 
--- a/transform/FeatureExtractionModelTransformer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/FeatureExtractionModelTransformer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -70,7 +70,7 @@
 
     void addFeature(int n,
                     sv_frame_t blockFrame,
-		    const Vamp::Plugin::Feature &feature);
+                    const Vamp::Plugin::Feature &feature);
 
     void setCompletion(int, int);
 
--- a/transform/FileFeatureWriter.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/FileFeatureWriter.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -46,7 +46,7 @@
         } else if (m_support & SupportOneFileTotal) {
             m_singleFileName = QString("output.%1").arg(m_extension);
         } else {
-            cerr << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
+            SVCERR << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
         }
     }
 }
@@ -130,7 +130,7 @@
             if (m_support & SupportOneFilePerTrackTransform &&
                 m_support & SupportOneFilePerTrack) {
                 if (m_singleFileName != "") {
-                    cerr << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
+                    SVCERR << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
                 } else {
                     m_manyFiles = true;
                 }
@@ -144,7 +144,7 @@
                     // OneFilePerTrack), so we need to be able to
                     // override it
 //                    if (m_manyFiles) {
-//                        cerr << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
+//                        SVCERR << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
 //                    } else {
                         m_singleFileName = i->second.c_str();
 //                    }
@@ -153,7 +153,7 @@
         } else if (i->first == "stdout") {
             if (m_support & SupportStdOut) {
                 if (m_singleFileName != "") {
-                    cerr << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
+                    SVCERR << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
                 } else {
                     m_stdout = true;
                 }
@@ -172,8 +172,8 @@
 {
     if (m_singleFileName != "") {
         if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
-            cerr << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
-            cerr << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
+            SVCERR << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
+            SVCERR << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
             return "";
         }
         return m_singleFileName;
@@ -219,8 +219,8 @@
     filename = QDir(dirname).filePath(filename);
 
     if (QFileInfo(filename).exists() && !(m_force || m_append)) {
-        cerr << endl << "FileFeatureWriter: ERROR: Output file \"" << filename << "\" exists (for input file or URL \"" << trackId << "\" and transform \"" << transformId << "\") and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append is specified -- not overwriting" << endl;
-        cerr << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
+        SVCERR << endl << "FileFeatureWriter: ERROR: Output file \"" << filename << "\" exists (for input file or URL \"" << trackId << "\" and transform \"" << transformId << "\") and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append is specified -- not overwriting" << endl;
+        SVCERR << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
         return "";
     }
     
@@ -300,7 +300,7 @@
         if (m_append) mode |= QIODevice::Append;
                        
         if (!file->open(mode)) {
-            cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
+            SVCERR << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
                  << "\" for writing" << endl;
             delete file;
             m_files[key] = 0;
--- a/transform/ModelTransformer.h	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/ModelTransformer.h	Mon Sep 17 13:51:14 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _TRANSFORMER_H_
-#define _TRANSFORMER_H_
+#ifndef SV_MODEL_TRANSFORMER_H
+#define SV_MODEL_TRANSFORMER_H
 
 #include "base/Thread.h"
 
--- a/transform/ModelTransformerFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/ModelTransformerFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -261,8 +261,8 @@
 //    SVDEBUG << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << endl;
 
     if (!transformer) {
-	cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << endl;
-	return;
+        cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << endl;
+        return;
     }
 
     if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
--- a/transform/RealTimeEffectModelTransformer.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/RealTimeEffectModelTransformer.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -47,15 +47,15 @@
 
     QString pluginId = transform.getPluginIdentifier();
 
-//    SVDEBUG << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId << ", output " << output << endl;
+    SVDEBUG << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId << ", output " << transform.getOutput() << endl;
 
     RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
+        RealTimePluginFactory::instanceFor(pluginId);
 
     if (!factory) {
-	cerr << "RealTimeEffectModelTransformer: No factory available for plugin id \""
-		  << pluginId << "\"" << endl;
-	return;
+        SVCERR << "RealTimeEffectModelTransformer: No factory available for plugin id \""
+               << pluginId << "\"" << endl;
+        return;
     }
 
     DenseTimeValueModel *input = getConformingInput();
@@ -67,9 +67,9 @@
                                           input->getChannelCount());
 
     if (!m_plugin) {
-	cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
-             << pluginId << "\"" << endl;
-	return;
+        SVCERR << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
+               << pluginId << "\"" << endl;
+        return;
     }
 
     TransformFactory::getInstance()->setPluginParameters(transform, m_plugin);
@@ -93,7 +93,7 @@
         m_outputs.push_back(model);
 
     } else {
-	
+        
         SparseTimeValueModel *model = new SparseTimeValueModel
             (input->getSampleRate(), transform.getBlockSize(), 0.0, 0.0, false);
 
@@ -112,9 +112,9 @@
 RealTimeEffectModelTransformer::getConformingInput()
 {
     DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+        dynamic_cast<DenseTimeValueModel *>(getInputModel());
     if (!dtvm) {
-	SVDEBUG << "RealTimeEffectModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl;
+        SVDEBUG << "RealTimeEffectModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl;
     }
     return dtvm;
 }
@@ -129,13 +129,25 @@
         SVDEBUG << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << endl;
         usleep(500000);
     }
-    if (m_abandoned) return;
+    if (m_abandoned) {
+        return;
+    }
+    if (m_outputs.empty()) {
+        return;
+    }
+    
+    SparseTimeValueModel *stvm =
+        dynamic_cast<SparseTimeValueModel *>(m_outputs[0]);
+    WritableWaveFileModel *wwfm =
+        dynamic_cast<WritableWaveFileModel *>(m_outputs[0]);
 
-    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_outputs[0]);
-    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_outputs[0]);
-    if (!stvm && !wwfm) return;
+    if (!stvm && !wwfm) {
+        return;
+    }
 
-    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
+    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) {
+        return;
+    }
 
     sv_samplerate_t sampleRate = input->getSampleRate();
     int channelCount = input->getChannelCount();
@@ -183,13 +195,13 @@
     while (blockFrame < contextStart + contextDuration + latency &&
            !m_abandoned) {
 
-	int completion = int
-	    ((((blockFrame - contextStart) / blockSize) * 99) /
+        int completion = int
+            ((((blockFrame - contextStart) / blockSize) * 99) /
              (1 + ((contextDuration) / blockSize)));
 
-	sv_frame_t got = 0;
+        sv_frame_t got = 0;
 
-	if (channelCount == 1) {
+        if (channelCount == 1) {
             if (inbufs && inbufs[0]) {
                 auto data = input->getData
                     (m_input.getChannel(), blockFrame, blockSize);
@@ -206,7 +218,7 @@
                     }
                 }
             }
-	} else {
+        } else {
             if (inbufs && inbufs[0]) {
                 auto data = input->getMultiChannelData
                     (0, channelCount - 1, blockFrame, blockSize);
@@ -228,7 +240,7 @@
                     }
                 }
             }
-	}
+        }
 
 /*
         cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< endl;
@@ -281,15 +293,15 @@
             }
         }
 
-	if (blockFrame == contextStart || completion > prevCompletion) {
+        if (blockFrame == contextStart || completion > prevCompletion) {
             // This setCompletion is probably misusing the completion
             // terminology, just as it was for WritableWaveFileModel
-	    if (stvm) stvm->setCompletion(completion);
-	    if (wwfm) wwfm->setWriteProportion(completion);
-	    prevCompletion = completion;
-	}
+            if (stvm) stvm->setCompletion(completion);
+            if (wwfm) wwfm->setWriteProportion(completion);
+            prevCompletion = completion;
+        }
         
-	blockFrame += blockSize;
+        blockFrame += blockSize;
     }
 
     if (m_abandoned) return;
--- a/transform/TransformFactory.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/transform/TransformFactory.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -112,20 +112,20 @@
 
     std::set<TransformDescription> dset;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
+         i != m_transforms.end(); ++i) {
 #ifdef DEBUG_TRANSFORM_FACTORY
         cerr << "inserting transform into set: id = " << i->second.identifier << endl;
 #endif
-	dset.insert(i->second);
+        dset.insert(i->second);
     }
 
     TransformList list;
     for (std::set<TransformDescription>::const_iterator i = dset.begin();
-	 i != dset.end(); ++i) {
+         i != dset.end(); ++i) {
 #ifdef DEBUG_TRANSFORM_FACTORY
         cerr << "inserting transform into list: id = " << i->identifier << endl;
 #endif
-	list.push_back(*i);
+        list.push_back(*i);
     }
 
     return list;
@@ -158,20 +158,20 @@
     
     std::set<TransformDescription> dset;
     for (TransformDescriptionMap::const_iterator i = m_uninstalledTransforms.begin();
-	 i != m_uninstalledTransforms.end(); ++i) {
+         i != m_uninstalledTransforms.end(); ++i) {
 #ifdef DEBUG_TRANSFORM_FACTORY
         cerr << "inserting transform into set: id = " << i->second.identifier << endl;
 #endif
-	dset.insert(i->second);
+        dset.insert(i->second);
     }
 
     TransformList list;
     for (std::set<TransformDescription>::const_iterator i = dset.begin();
-	 i != dset.end(); ++i) {
+         i != dset.end(); ++i) {
 #ifdef DEBUG_TRANSFORM_FACTORY
         cerr << "inserting transform into uninstalled list: id = " << i->identifier << endl;
 #endif
-	list.push_back(*i);
+        list.push_back(*i);
     }
 
     return list;
@@ -248,7 +248,7 @@
 
     std::set<TransformDescription::Type> types;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
+         i != m_transforms.end(); ++i) {
         types.insert(i->second.type);
     }
 
@@ -376,17 +376,17 @@
          i != transforms.end(); ++i) {
 
         TransformDescription desc = i->second;
-	QString identifier = desc.identifier;
+        QString identifier = desc.identifier;
         QString maker = desc.maker;
 
         QString td = desc.name;
         QString tn = td.section(": ", 0, 0);
         QString to = td.section(": ", 1);
 
-	if (names[tn] > 1) {
+        if (names[tn] > 1) {
             maker.replace(QRegExp(tr(" [\\(<].*$")), "");
-	    tn = QString("%1 [%2]").arg(tn).arg(maker);
-	}
+            tn = QString("%1 [%2]").arg(tn).arg(maker);
+        }
 
         if (to != "") {
             desc.name = QString("%1: %2").arg(tn).arg(to);
@@ -394,8 +394,8 @@
             desc.name = tn;
         }
 
-	m_transforms[identifier] = desc;
-    }	    
+        m_transforms[identifier] = desc;
+    }            
 
     m_transformsPopulated = true;
 }
@@ -416,7 +416,7 @@
 
     for (int i = 0; i < (int)plugs.size(); ++i) {
 
-	QString pluginId = plugs[i];
+        QString pluginId = plugs[i];
 
         piper_vamp::PluginStaticData psd = factory->getPluginStaticData(pluginId);
 
@@ -434,10 +434,10 @@
 
             QString outputName = QString::fromStdString(o.name);
 
-	    QString transformId = QString("%1:%2")
+            QString transformId = QString("%1:%2")
                 .arg(pluginId).arg(QString::fromStdString(o.identifier));
 
-	    QString userName;
+            QString userName;
             QString friendlyName;
 //!!! return to this            QString units = outputs[j].unit.c_str();
             QString description = QString::fromStdString(psd.basic.description);
@@ -464,13 +464,13 @@
                 }
             }                    
 
-	    if (basicOutputs.size() == 1) {
-		userName = pluginName;
+            if (basicOutputs.size() == 1) {
+                userName = pluginName;
                 friendlyName = pluginName;
-	    } else {
-		userName = QString("%1: %2").arg(pluginName).arg(outputName);
+            } else {
+                userName = QString("%1: %2").arg(pluginName).arg(outputName);
                 friendlyName = outputName;
-	    }
+            }
 
             bool configurable = (!psd.programs.empty() ||
                                  !psd.parameters.empty());
@@ -479,7 +479,7 @@
             cerr << "Feature extraction plugin transform: " << transformId << " friendly name: " << friendlyName << endl;
 #endif
 
-	    transforms[transformId] = 
+            transforms[transformId] = 
                 TransformDescription(TransformDescription::Analysis,
                                      category,
                                      transformId,
@@ -491,7 +491,7 @@
 //!!!                                     units,
                                      "",
                                      configurable);
-	}
+        }
     }
 }
 
@@ -499,37 +499,37 @@
 TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms)
 {
     std::vector<QString> plugs =
-	RealTimePluginFactory::getAllPluginIdentifiers();
+        RealTimePluginFactory::getAllPluginIdentifiers();
     if (m_exiting) return;
 
     static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
 
     for (int i = 0; i < (int)plugs.size(); ++i) {
         
-	QString pluginId = plugs[i];
+        QString pluginId = plugs[i];
 
         RealTimePluginFactory *factory =
             RealTimePluginFactory::instanceFor(pluginId);
 
-	if (!factory) {
-	    cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId << endl;
-	    continue;
-	}
+        if (!factory) {
+            cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId << endl;
+            continue;
+        }
 
         const RealTimePluginDescriptor *descriptor =
             factory->getPluginDescriptor(pluginId);
 
         if (!descriptor) {
-	    cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId << endl;
-	    continue;
-	}
-	
+            cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId << endl;
+            continue;
+        }
+        
 //!!!        if (descriptor->controlOutputPortCount == 0 ||
 //            descriptor->audioInputPortCount == 0) continue;
 
 //        cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << endl;
-	
-	QString pluginName = descriptor->name.c_str();
+        
+        QString pluginName = descriptor->name.c_str();
         QString category = factory->getPluginCategory(pluginId);
         bool configurable = (descriptor->parameterCount > 0);
         QString maker = descriptor->maker.c_str();
@@ -856,7 +856,7 @@
 TransformFactory::getTransformName(TransformId identifier)
 {
     if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].name;
+        return m_transforms[identifier].name;
     } else return "";
 }
 
@@ -864,7 +864,7 @@
 TransformFactory::getTransformFriendlyName(TransformId identifier)
 {
     if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].friendlyName;
+        return m_transforms[identifier].friendlyName;
     } else return "";
 }
 
@@ -872,7 +872,7 @@
 TransformFactory::getTransformUnits(TransformId identifier)
 {
     if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].units;
+        return m_transforms[identifier].units;
     } else return "";
 }
 
@@ -880,7 +880,7 @@
 TransformFactory::getTransformInfoUrl(TransformId identifier)
 {
     if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].infoUrl;
+        return m_transforms[identifier].infoUrl;
     } else return "";
 }
 
@@ -913,7 +913,7 @@
 TransformFactory::isTransformConfigurable(TransformId identifier)
 {
     if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].configurable;
+        return m_transforms[identifier].configurable;
     } else return false;
 }