changeset 1056:c4898e57eea5 tonioni

Merge from default branch
author Chris Cannam
date Mon, 23 Mar 2015 10:04:48 +0000
parents ba404199345f (current diff) 682d64f05e72 (diff)
children 5c5d4863b428
files
diffstat 33 files changed, 997 insertions(+), 225 deletions(-) [+]
line wrap: on
line diff
--- a/base/Pitch.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/Pitch.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -19,45 +19,49 @@
 
 #include <cmath>
 
-float
+double
 Pitch::getFrequencyForPitch(int midiPitch,
-			    float centsOffset,
-			    float concertA)
+			    double centsOffset,
+			    double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float p = float(midiPitch) + (centsOffset / 100);
-    return concertA * powf(2.0, (p - 69.0) / 12.0);
+    double p = double(midiPitch) + (centsOffset / 100);
+    return concertA * pow(2.0, (p - 69.0) / 12.0);
 }
 
 int
-Pitch::getPitchForFrequency(float frequency,
-			    float *centsOffsetReturn,
-			    float concertA)
+Pitch::getPitchForFrequency(double frequency,
+			    double *centsOffsetReturn,
+			    double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
 
-    int midiPitch = int(p + 0.00001);
-    float centsOffset = (p - midiPitch) * 100.0;
+    int midiPitch = int(round(p));
+    double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
 	midiPitch = midiPitch + 1;
 	centsOffset = -(100.0 - centsOffset);
     }
+    if (centsOffset < -50.0) {
+	midiPitch = midiPitch - 1;
+	centsOffset = (100.0 + centsOffset);
+    }
     
     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
     return midiPitch;
 }
 
 int
-Pitch::getPitchForFrequencyDifference(float frequencyA,
-                                      float frequencyB,
-                                      float *centsOffsetReturn,
-                                      float concertA)
+Pitch::getPitchForFrequencyDifference(double frequencyA,
+                                      double frequencyB,
+                                      double *centsOffsetReturn,
+                                      double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
@@ -67,13 +71,13 @@
         std::swap(frequencyA, frequencyB);
     }
 
-    float pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0;
-    float pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0;
 
-    float p = pB - pA;
+    double p = pB - pA;
 
     int midiPitch = int(p + 0.00001);
-    float centsOffset = (p - midiPitch) * 100.0;
+    double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
 	midiPitch = midiPitch + 1;
@@ -96,13 +100,19 @@
     "Ab%1", "A%1",  "Bb%1", "B%1"
 };
 
-QString
-Pitch::getPitchLabel(int midiPitch,
-		     float centsOffset,
-		     bool useFlats)
+int
+Pitch::getPitchForNoteAndOctave(int note, int octave)
 {
     int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
-    int octave = baseOctave;
+    return (octave - baseOctave) * 12 + note;
+}
+
+void
+Pitch::getNoteAndOctaveForPitch(int midiPitch, int &note, int &octave)
+{
+    int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
+
+    octave = baseOctave;
 
     // Note, this only gets the right octave number at octave
     // boundaries because Cb is enharmonic with B (not B#) and B# is
@@ -118,44 +128,55 @@
 	octave = midiPitch / 12 + baseOctave;
     }
 
-    QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave);
+    note = midiPitch % 12;
+}
 
-    int ic = lrintf(centsOffset);
+QString
+Pitch::getPitchLabel(int midiPitch,
+		     double centsOffset,
+		     bool useFlats)
+{
+    int note, octave;
+    getNoteAndOctaveForPitch(midiPitch, note, octave);
+
+    QString plain = (useFlats ? flatNotes : notes)[note].arg(octave);
+
+    int ic = lrint(centsOffset);
     if (ic == 0) return plain;
     else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic);
     else return QString("%1%2c").arg(plain).arg(ic);
 }
 
 QString
-Pitch::getPitchLabelForFrequency(float frequency,
-				 float concertA,
+Pitch::getPitchLabelForFrequency(double frequency,
+				 double concertA,
 				 bool useFlats)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float centsOffset = 0.0;
+    double centsOffset = 0.0;
     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
     return getPitchLabel(midiPitch, centsOffset, useFlats);
 }
 
 QString
-Pitch::getLabelForPitchRange(int semis, float cents)
+Pitch::getLabelForPitchRange(int semis, double cents)
 {
     if (semis > 0) {
-        while (cents < 0.f) {
+        while (cents < 0.0) {
             --semis;
-            cents += 100.f;
+            cents += 100.0;
         }
     }
     if (semis < 0) {
-        while (cents > 0.f) {
+        while (cents > 0.0) {
             ++semis;
-            cents -= 100.f;
+            cents -= 100.0;
         }
     }
 
-    int ic = lrintf(cents);
+    int ic = lrint(cents);
 
     if (ic == 0) {
         if (semis >= 12) {
@@ -181,10 +202,10 @@
 }
 
 bool
-Pitch::isFrequencyInMidiRange(float frequency,
-                              float concertA)
+Pitch::isFrequencyInMidiRange(double frequency,
+                              double concertA)
 {
-    float centsOffset = 0.0;
+    double centsOffset = 0.0;
     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
     return (midiPitch >= 0 && midiPitch < 128);
 }
--- a/base/Pitch.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/Pitch.h	Mon Mar 23 10:04:48 2015 +0000
@@ -30,9 +30,9 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static float getFrequencyForPitch(int midiPitch,
-				      float centsOffset = 0,
-				      float concertA = 0.0);
+    static double getFrequencyForPitch(int midiPitch,
+				      double centsOffset = 0,
+				      double concertA = 0.0);
 
     /**
      * Return the nearest MIDI pitch to the given frequency.
@@ -46,9 +46,22 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static int getPitchForFrequency(float frequency,
-				    float *centsOffsetReturn = 0,
-				    float concertA = 0.0);
+    static int getPitchForFrequency(double frequency,
+				    double *centsOffsetReturn = 0,
+				    double concertA = 0.0);
+
+    /**
+     * Compatibility version of getPitchForFrequency accepting float
+     * pointer argument.
+     */
+    static int getPitchForFrequency(double frequency,
+				    float *centsOffsetReturn,
+				    double concertA = 0.0) {
+        double c;
+        int p = getPitchForFrequency(frequency, &c, concertA);
+        if (centsOffsetReturn) *centsOffsetReturn = float(c);
+        return p;
+    }
 
     /**
      * Return the nearest MIDI pitch range to the given frequency
@@ -64,10 +77,41 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static int getPitchForFrequencyDifference(float frequencyA,
-                                              float frequencyB,
-                                              float *centsOffsetReturn = 0,
-                                              float concertA = 0.0);
+    static int getPitchForFrequencyDifference(double frequencyA,
+                                              double frequencyB,
+                                              double *centsOffsetReturn = 0,
+                                              double concertA = 0.0);
+
+    /**
+     * Compatibility version of getPitchForFrequencyDifference
+     * accepting float pointer argument.
+     */
+    static int getPitchForFrequencyDifference(double frequencyA,
+                                              double frequencyB,
+                                              float *centsOffsetReturn,
+                                              double concertA = 0.0) {
+        double c;
+        int p = getPitchForFrequencyDifference(frequencyA, frequencyB,
+                                               &c, concertA);
+        if (centsOffsetReturn) *centsOffsetReturn = float(c);
+        return p;
+    }
+    
+    /**
+     * Return the MIDI pitch for the given note number (0-12 where 0
+     * is C) and octave number. The octave numbering system is based
+     * on the application preferences (default is C4 = middle C,
+     * though in previous SV releases that was C3).
+     */
+    static int getPitchForNoteAndOctave(int note, int octave);
+    
+    /**
+     * Return the note number (0-12 where 0 is C) and octave number
+     * for the given MIDI pitch. The octave numbering system is based
+     * on the application preferences (default is C4 = middle C,
+     * though in previous SV releases that was C3).
+     */
+    static void getNoteAndOctaveForPitch(int midiPitch, int &note, int &octave);
 
     /**
      * Return a string describing the given MIDI pitch, with optional
@@ -83,9 +127,9 @@
      * e.g. Bb3 instead of A#3.
      */
     static QString getPitchLabel(int midiPitch,
-				 float centsOffset = 0,
+				 double centsOffset = 0,
 				 bool useFlats = false);
-
+    
     /**
      * Return a string describing the nearest MIDI pitch to the given
      * frequency, with cents offset.
@@ -97,15 +141,15 @@
      * If useFlats is true, spell notes with flats instead of sharps,
      * e.g. Bb3 instead of A#3.
      */
-    static QString getPitchLabelForFrequency(float frequency,
-					     float concertA = 0.0,
+    static QString getPitchLabelForFrequency(double frequency,
+					     double concertA = 0.0,
 					     bool useFlats = false);
 
     /**
      * Return a string describing the given pitch range in octaves,
      * semitones and cents.  This is in the form e.g. "1'2+4c".
      */
-    static QString getLabelForPitchRange(int semis, float cents = 0);
+    static QString getLabelForPitchRange(int semis, double cents = 0);
 
     /**
      * Return true if the given frequency falls within the range of
@@ -118,8 +162,8 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static bool isFrequencyInMidiRange(float frequency,
-                                       float concertA = 0.0);
+    static bool isFrequencyInMidiRange(double frequency,
+                                       double concertA = 0.0);
 };
 
 
--- a/base/Preferences.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/Preferences.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -49,6 +49,7 @@
     m_viewFontSize(10),
     m_backgroundMode(BackgroundFromTheme),
     m_timeToTextMode(TimeToTextMs),
+    m_showHMS(true),
     m_octave(4),
     m_showSplash(true)
 {
@@ -71,6 +72,7 @@
         (settings.value("background-mode", int(BackgroundFromTheme)).toInt());
     m_timeToTextMode = TimeToTextMode
         (settings.value("time-to-text-mode", int(TimeToTextMs)).toInt());
+    m_showHMS = (settings.value("show-hours-minutes-seconds", true)).toBool(); 
     m_octave = (settings.value("octave-of-middle-c", 4)).toInt();
     m_viewFontSize = settings.value("view-font-size", 10).toInt();
     m_showSplash = settings.value("show-splash", true).toBool();
@@ -102,6 +104,7 @@
     props.push_back("Temporary Directory Root");
     props.push_back("Background Mode");
     props.push_back("Time To Text Mode");
+    props.push_back("Show Hours And Minutes");
     props.push_back("Octave Numbering System");
     props.push_back("View Font Size");
     props.push_back("Show Splash Screen");
@@ -148,7 +151,10 @@
         return tr("Background colour preference");
     }
     if (name == "Time To Text Mode") {
-        return tr("Time display format");
+        return tr("Time display precision");
+    }
+    if (name == "Show Hours And Minutes") {
+        return tr("Use hours:minutes:seconds format");
     }
     if (name == "Octave Numbering System") {
         return tr("Label middle C as");
@@ -205,6 +211,9 @@
     if (name == "Time To Text Mode") {
         return ValueProperty;
     }
+    if (name == "Show Hours And Minutes") {
+        return ToggleProperty;
+    }
     if (name == "Octave Numbering System") {
         return ValueProperty;
     }
@@ -259,6 +268,7 @@
 
     if (name == "Omit Temporaries from Recent Files") {
         if (deflt) *deflt = 1;
+        return m_omitRecentTemps ? 1 : 0;
     }
 
     if (name == "Background Mode") {
@@ -275,6 +285,11 @@
         return int(m_timeToTextMode);
     }        
 
+    if (name == "Show Hours And Minutes") {
+        if (deflt) *deflt = 1;
+        return m_showHMS ? 1 : 0;
+    }
+    
     if (name == "Octave Numbering System") {
         // we don't support arbitrary octaves in the gui, because we
         // want to be able to label what the octave system comes
@@ -294,6 +309,7 @@
 
     if (name == "Show Splash Screen") {
         if (deflt) *deflt = 1;
+        return m_showSplash ? 1 : 0;
     }
 
     return 0;
@@ -404,6 +420,8 @@
         setBackgroundMode(BackgroundMode(value));
     } else if (name == "Time To Text Mode") {
         setTimeToTextMode(TimeToTextMode(value));
+    } else if (name == "Show Hours And Minutes") {
+        setShowHMS(value ? true : false);
     } else if (name == "Octave Numbering System") {
         setOctaveOfMiddleC(getOctaveOfMiddleCInSystem
                            (OctaveNumberingSystem(value)));
@@ -599,6 +617,21 @@
 }
 
 void
+Preferences::setShowHMS(bool show)
+{
+    if (m_showHMS != show) {
+
+        m_showHMS = show;
+
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("show-hours-minutes-seconds", show);
+        settings.endGroup();
+        emit propertyChanged("Show Hours And Minutes");
+    }
+}
+
+void
 Preferences::setOctaveOfMiddleC(int oct)
 {
     if (m_octave != oct) {
--- a/base/Preferences.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/Preferences.h	Mon Mar 23 10:04:48 2015 +0000
@@ -93,6 +93,8 @@
     };
     TimeToTextMode getTimeToTextMode() const { return m_timeToTextMode; }
 
+    bool getShowHMS() const { return m_showHMS; }
+    
     int getOctaveOfMiddleC() const {
         // weed out unsupported octaves
         return getOctaveOfMiddleCInSystem(getSystemWithMiddleCInOctave(m_octave));
@@ -119,6 +121,7 @@
     void setNormaliseAudio(bool);
     void setBackgroundMode(BackgroundMode mode);
     void setTimeToTextMode(TimeToTextMode mode);
+    void setShowHMS(bool show);
     void setOctaveOfMiddleC(int oct);
     void setViewFontSize(int size);
     void setShowSplash(bool);
@@ -156,6 +159,7 @@
     int m_viewFontSize;
     BackgroundMode m_backgroundMode;
     TimeToTextMode m_timeToTextMode;
+    bool m_showHMS;
     int m_octave;
     bool m_showSplash;
 };
--- a/base/RealTime.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/RealTime.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -59,7 +59,11 @@
 RealTime
 RealTime::fromSeconds(double sec)
 {
-    return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+    if (sec >= 0) {
+        return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+    } else {
+        return -fromSeconds(-sec);
+    }
 }
 
 RealTime
@@ -270,19 +274,25 @@
 
     std::stringstream out;
 
-    if (sec >= 3600) {
-	out << (sec / 3600) << ":";
+    if (p->getShowHMS()) {
+    
+        if (sec >= 3600) {
+            out << (sec / 3600) << ":";
+        }
+
+        if (sec >= 60) {
+            out << (sec % 3600) / 60 << ":";
+        }
+
+        if (sec >= 10) {
+            out << ((sec % 60) / 10);
+        }
+
+        out << (sec % 10);
+
+    } else {
+        out << sec;
     }
-
-    if (sec >= 60) {
-	out << (sec % 3600) / 60 << ":";
-    }
-
-    if (sec >= 10) {
-	out << ((sec % 60) / 10);
-    }
-
-    out << (sec % 10);
     
     int ms = msec();
 
@@ -315,21 +325,29 @@
 {
     if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps);
 
+    Preferences *p = Preferences::getInstance();
+
     std::stringstream out;
 
-    if (sec >= 3600) {
-	out << (sec / 3600) << ":";
+    if (p->getShowHMS()) {
+    
+        if (sec >= 3600) {
+            out << (sec / 3600) << ":";
+        }
+
+        if (sec >= 60) {
+            out << (sec % 3600) / 60 << ":";
+        }
+
+        if (sec >= 10) {
+            out << ((sec % 60) / 10);
+        }
+
+        out << (sec % 10);
+
+    } else {
+        out << sec;
     }
-
-    if (sec >= 60) {
-	out << (sec % 3600) / 60 << ":";
-    }
-
-    if (sec >= 10) {
-	out << ((sec % 60) / 10);
-    }
-
-    out << (sec % 10);
     
     int f = nsec / (ONE_BILLION / fps);
 
--- a/base/StringBits.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/StringBits.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -20,6 +20,10 @@
 
 #include "StringBits.h"
 
+#include "Debug.h"
+
+using namespace std;
+
 double
 StringBits::stringToDoubleLocaleFree(QString s, bool *ok)
 {
@@ -73,6 +77,11 @@
     QStringList tokens;
     QString tok;
 
+    // sep -> just seen a field separator (or start of line)
+    // unq -> in an unquoted field
+    // q1  -> in a single-quoted field
+    // q2  -> in a double-quoted field
+
     enum { sep, unq, q1, q2 } mode = sep;
 
     for (int i = 0; i < s.length(); ++i) {
@@ -83,14 +92,14 @@
 	    switch (mode) {
 	    case sep: mode = q1; break;
 	    case unq: case q2: tok += c; break;
-	    case q1: mode = sep; tokens << tok; tok = ""; break;
+	    case q1: mode = unq; break;
 	    }
 
 	} else if (c == '"') {
 	    switch (mode) {
 	    case sep: mode = q2; break;
 	    case unq: case q1: tok += c; break;
-	    case q2: mode = sep; tokens << tok; tok = ""; break;
+	    case q2: mode = unq; break;
 	    }
 
 	} else if (c == separator || (separator == ' ' && c.isSpace())) {
@@ -117,86 +126,19 @@
 	}
     }
 
-    if (tok != "" || mode != sep) tokens << tok;
+    if (tok != "" || mode != sep) {
+        if (mode == q1) {
+            tokens << ("'" + tok);  // turns out it wasn't quoted after all
+        } else if (mode == q2) {
+            tokens << ("\"" + tok);
+        } else {
+            tokens << tok;
+        }
+    }
+
     return tokens;
 }
 
-/*
-
-void testSplit()
-{
-    QStringList tests;
-    tests << "a b c d";
-    tests << "a \"b c\" d";
-    tests << "a 'b c' d";
-    tests << "a \"b c\\\" d\"";
-    tests << "a 'b c\\' d'";
-    tests << "a \"b c' d\"";
-    tests << "a 'b c\" d'";
-    tests << "aa 'bb cc\" dd'";
-    tests << "a'a 'bb' \\\"cc\" dd\\\"";
-    tests << "  a'a \\\'	 'bb'	 \'	\\\"cc\" ' dd\\\" '";
-
-    for (int j = 0; j < tests.size(); ++j) {
-	cout << endl;
-	cout << tests[j] << endl;
-	cout << "->" << endl << "(";
-	QStringList l = splitQuoted(tests[j], ' ');
-	for (int i = 0; i < l.size(); ++i) {
-	    if (i > 0) cout << ";";
-	    cout << l[i];
-	}
-	cout << ")" << endl;
-    }
-}
-
-*/
-
-/* 
-   Results:
-
-a b c d
-->     
-(a;b;c;d)
-
-a "b c" d
-->       
-(a;b c;d)
-
-a 'b c' d
-->       
-(a;b c;d)
-
-a "b c\" d"
-->         
-(a;b c" d) 
-
-a 'b c\' d'
-->         
-(a;b c' d) 
-
-a "b c' d"
-->        
-(a;b c' d)
-
-a 'b c" d'
-->        
-(a;b c" d)
-
-aa 'bb cc" dd'
-->            
-(aa;bb cc" dd)
-
-a'a 'bb' \"cc" dd\"
-->                 
-(a'a;bb;"cc";dd")  
-
-  a'a \'         'bb'    '      \"cc" ' dd\" '
-->                                            
-(a'a;';bb;      "cc" ;dd";)
-
-*/
-
 QStringList
 StringBits::split(QString line, QChar separator, bool quoted)
 {
--- a/base/test/TestPitch.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/test/TestPitch.h	Mon Mar 23 10:04:48 2015 +0000
@@ -72,18 +72,29 @@
 	QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
     }
 
-#define MIDDLE_C 261.6255653f
+#define MIDDLE_C 261.6255653005986
 
     void frequencyForPitch()
     {
 	QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.f);
-	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.f);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.f);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.0);
+	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.0);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.0);
     }
 
     void pitchForFrequency()
     {
+	double centsOffset = 0.0;
+	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+	QCOMPARE(centsOffset, 0.0);
+	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+	QCOMPARE(int(centsOffset), -4);
+	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+	QCOMPARE(centsOffset, 0.0);
+    }
+
+    void pitchForFrequencyF()
+    {
 	float centsOffset = 0.f;
 	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
 	QCOMPARE(centsOffset, 0.f);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestRealTime.h	Mon Mar 23 10:04:48 2015 +0000
@@ -0,0 +1,301 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef TEST_REALTIME_H
+#define TEST_REALTIME_H
+
+#include "../RealTime.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestRealTime : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+#define ONE_MILLION 1000000
+#define ONE_BILLION 1000000000
+    
+    void zero()
+    {
+	QCOMPARE(RealTime(0, 0), RealTime::zeroTime);
+	QCOMPARE(RealTime(0, 0).sec, 0);
+	QCOMPARE(RealTime(0, 0).nsec, 0);
+	QCOMPARE(RealTime(0, 0).msec(), 0);
+	QCOMPARE(RealTime(0, 0).usec(), 0);
+    }
+
+    void ctor()
+    {
+	QCOMPARE(RealTime(0, 0), RealTime(0, 0));
+
+	// wraparounds
+	QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0));
+	QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0));
+
+	QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0));
+	QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0));
+	QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+	
+	QCOMPARE(RealTime(0, 1).sec, 0);
+	QCOMPARE(RealTime(0, 1).nsec, 1);
+	QCOMPARE(RealTime(0, -1).sec, 0);
+	QCOMPARE(RealTime(0, -1).nsec, -1);
+	QCOMPARE(RealTime(1, -1).sec, 0);
+	QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1);
+	QCOMPARE(RealTime(-1, 1).sec, 0);
+	QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1);
+	QCOMPARE(RealTime(-1, -1).sec, -1);
+	QCOMPARE(RealTime(-1, -1).nsec, -1);
+	
+	QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0);
+	QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0);
+	QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1);
+	QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2);
+
+	QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0);
+	QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0);
+	QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1);
+	QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2);
+    }
+    
+    void fromSeconds()
+    {
+	QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0));
+
+	QCOMPARE(RealTime::fromSeconds(0.5).sec, 0);
+	QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500);
+	
+	QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0));
+	QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0);
+	QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500);
+	
+	QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1);
+	QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500);
+	
+	QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0));
+	QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void fromMilliseconds()
+    {
+	QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0));
+	QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0));
+	QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2));
+
+    	QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0));
+	QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0));
+	QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2));
+    }
+    
+    void fromTimeval()
+    {
+	struct timeval tv;
+
+	tv.tv_sec = 0; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0));
+	tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2));
+	tv.tv_sec = 1; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0));
+	tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2));
+
+	tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2));
+	tv.tv_sec = -1; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0));
+	tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void fromXsdDuration()
+    {
+	QCOMPARE(RealTime::fromXsdDuration("PT0"), RealTime::zeroTime);
+	QCOMPARE(RealTime::fromXsdDuration("PT0S"), RealTime::zeroTime);
+	QCOMPARE(RealTime::fromXsdDuration("PT10S"), RealTime(10, 0));
+	QCOMPARE(RealTime::fromXsdDuration("PT10.5S"), RealTime(10, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromXsdDuration("PT1.5S").sec, 1);
+	QCOMPARE(RealTime::fromXsdDuration("PT1.5S").msec(), 500);
+	QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").sec, -1);
+	QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").msec(), -500);
+	QCOMPARE(RealTime::fromXsdDuration("PT1M30.5S"), RealTime(90, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromXsdDuration("PT1H2M30.5S"), RealTime(3750, ONE_BILLION/2));
+    }
+
+    void toDouble()
+    {
+	QCOMPARE(RealTime(0, 0).toDouble(), 0.0);
+	QCOMPARE(RealTime(0, ONE_BILLION/2).toDouble(), 0.5);
+	QCOMPARE(RealTime(1, 0).toDouble(), 1.0);
+	QCOMPARE(RealTime(1, ONE_BILLION/2).toDouble(), 1.5);
+
+	QCOMPARE(RealTime(0, -ONE_BILLION/2).toDouble(), -0.5);
+	QCOMPARE(RealTime(-1, 0).toDouble(), -1.0);
+	QCOMPARE(RealTime(-1, -ONE_BILLION/2).toDouble(), -1.5);
+    }
+
+    void assign()
+    {
+	RealTime r;
+	r = RealTime(0, 0);
+	QCOMPARE(r, RealTime::zeroTime);
+	r = RealTime(0, ONE_BILLION/2);
+	QCOMPARE(r.toDouble(), 0.5);
+	r = RealTime(-1, -ONE_BILLION/2);
+	QCOMPARE(r.toDouble(), -1.5);
+    }
+
+    void plus()
+    {
+	QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0));
+
+	QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0));
+	QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+
+    	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2));
+    }
+    
+    void minus()
+    {
+	QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0));
+
+	QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+
+    	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void negate()
+    {
+	QCOMPARE(-RealTime(0, 0), RealTime(0, 0));
+	QCOMPARE(-RealTime(1, 0), RealTime(-1, 0));
+	QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+	QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+    }
+
+    void compare()
+    {
+	int sec, nsec;
+	for (sec = -2; sec <= 2; sec += 2) {
+	    for (nsec = -1; nsec <= 1; nsec += 1) {
+		QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true);
+		QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true);
+		QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true);
+	    }
+	}
+	RealTime prev(-3, 0);
+	for (sec = -2; sec <= 2; sec += 2) {
+	    for (nsec = -1; nsec <= 1; nsec += 1) {
+
+		RealTime curr(sec, nsec);
+
+		QCOMPARE(prev < curr, true);
+		QCOMPARE(prev > curr, false);
+		QCOMPARE(prev == curr, false);
+		QCOMPARE(prev != curr, true);
+		QCOMPARE(prev <= curr, true);
+		QCOMPARE(prev >= curr, false);
+
+		QCOMPARE(curr < prev, false);
+		QCOMPARE(curr > prev, true);
+		QCOMPARE(curr == prev, false);
+		QCOMPARE(curr != prev, true);
+		QCOMPARE(curr <= prev, false);
+		QCOMPARE(curr >= prev, true);
+
+		prev = curr;
+	    }
+	}
+    }
+
+    void frame()
+    {
+        int frames[] = {
+            0, 1, 2047, 2048, 6656, 32767, 32768, 44100, 44101, 999999999
+        };
+        int n = sizeof(frames)/sizeof(frames[0]);
+
+        int rates[] = {
+            1, 2, 8000, 22050, 44100, 44101, 192000
+        };
+        int m = sizeof(rates)/sizeof(rates[0]);
+
+        for (int i = 0; i < n; ++i) {
+            int frame = frames[i];
+            for (int j = 0; j < m; ++j) {
+                int rate = rates[j];
+
+                RealTime rt = RealTime::frame2RealTime(frame, rate);
+                int conv = RealTime::realTime2Frame(rt, rate);
+                QCOMPARE(frame, conv);
+
+                rt = RealTime::frame2RealTime(-frame, rate);
+                conv = RealTime::realTime2Frame(rt, rate);
+                QCOMPARE(-frame, conv);
+            }
+        }
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestStringBits.h	Mon Mar 23 10:04:48 2015 +0000
@@ -0,0 +1,203 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef TEST_STRINGBITS_H
+#define TEST_STRINGBITS_H
+
+#include "../StringBits.h"
+
+#include <QObject>
+#include <QStringList>
+#include <QtTest>
+
+#include <iostream>
+
+using namespace std;
+
+class TestStringBits : public QObject
+{
+    Q_OBJECT
+
+private:
+    void testSplitQuoted(QString in, QStringList out) {
+        // Only suitable where the output strings do not have
+        // consecutive spaces in them
+        QCOMPARE(StringBits::splitQuoted(in, ' '), out);
+        QString in2(in);
+        in2.replace(' ', ',');
+        QStringList out2;
+        foreach (QString o, out) {
+            out2 << o.replace(' ', ',');
+        }
+        QCOMPARE(StringBits::splitQuoted(in2, ','), out2);
+    }
+
+private slots:
+    void simple() {
+        QString in = "a b c d";
+        QStringList out;     
+        out << "a" << "b" << "c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void dquoted() {
+        QString in = "a \"b c\" d";
+        QStringList out;       
+        out << "a" << "b c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void drunon() {
+        QString in = "a \"b c\"d e";
+        QStringList out;       
+        out << "a" << "b cd" << "e";
+        testSplitQuoted(in, out);
+    }
+
+    void squoted() {
+        QString in = "a 'b c' d";
+        QStringList out;       
+        out << "a" << "b c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void srunon() {
+        QString in = "a 'b c'd e";
+        QStringList out;       
+        out << "a" << "b cd" << "e";
+        testSplitQuoted(in, out);
+    }
+
+    void dempty() {
+        QString in = "\"\" \"\" \"\"";
+        QStringList out;       
+        out << "" << "" << "";
+        testSplitQuoted(in, out);
+    }
+
+    void sempty() {
+        QString in = "'' '' ''";
+        QStringList out;       
+        out << "" << "" << "";
+        testSplitQuoted(in, out);
+    }
+
+    void descaped() {
+        QString in = "a \"b c\\\" d\"";
+        QStringList out;         
+        out << "a" << "b c\" d"; 
+        testSplitQuoted(in, out);
+    }
+
+    void sescaped() {
+        QString in = "a 'b c\\' d'";
+        QStringList out;         
+        out << "a" << "b c' d"; 
+        testSplitQuoted(in, out);
+    }
+
+    void dnested() {
+        QString in = "a \"b c' d\"";
+        QStringList out;        
+        out << "a" << "b c' d";
+        testSplitQuoted(in, out);
+    }
+
+    void snested() {
+        QString in = "a 'b c\" d'";
+        QStringList out;        
+        out << "a" << "b c\" d";
+        testSplitQuoted(in, out);
+    }
+
+    void snested2() {
+        QString in = "aa 'bb cc\" dd'";
+        QStringList out;            
+        out << "aa" << "bb cc\" dd";
+        testSplitQuoted(in, out);
+    }
+
+    void qquoted() {
+        QString in = "a'a 'bb' \\\"cc\" dd\\\"";
+        QStringList out;                 
+        out << "a'a" << "bb" << "\"cc\"" << "dd\"";  
+        testSplitQuoted(in, out);
+    }
+
+    void multispace() {
+        QString in = "  a'a \\'         'bb'    '      \\\"cc\" ' dd\\\" '";
+        QStringList out;                                            
+        out << "a'a" << "'" << "bb" << "      \"cc\" " << "dd\"" << "'";
+        QCOMPARE(StringBits::splitQuoted(in, ' '), out);
+
+        QString in2 = ",,a'a,\\',,,,,,,,,'bb',,,,',,,,,,\\\"cc\",',dd\\\",'";
+        QStringList out2;
+        out2 << "" << "" << "a'a" << "'" << "" << "" << "" << "" << "" << ""
+             << "" << "" << "bb" << "" << "" << "" << ",,,,,,\"cc\","
+             << "dd\"" << "'";
+        QCOMPARE(StringBits::splitQuoted(in2, ','), out2);
+    }
+};
+
+#endif
+
+/* r928
+Config: Using QtTest library 5.3.2, Qt 5.3.2
+PASS   : TestStringBits::initTestCase()
+PASS   : TestStringBits::simple()
+PASS   : TestStringBits::dquoted()
+PASS   : TestStringBits::squoted()
+PASS   : TestStringBits::descaped()
+FAIL!  : TestStringBits::sescaped() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 3
+   Expected (out) size: 2
+   Loc: [o/../TestStringBits.h(65)]
+PASS   : TestStringBits::dnested()
+PASS   : TestStringBits::snested()
+PASS   : TestStringBits::snested2()
+PASS   : TestStringBits::qquoted()
+FAIL!  : TestStringBits::multispace() Compared lists differ at index 1.
+   Actual   (StringBits::splitQuoted(in, ' ')): "         "
+   Expected (out): "'"
+   Loc: [o/../TestStringBits.h(100)]
+FAIL!  : TestStringBits::qcommas() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ',')) size: 4
+   Expected (out) size: 3
+   Loc: [o/../TestStringBits.h(107)]
+PASS   : TestStringBits::cleanupTestCase()
+Totals: 10 passed, 3 failed, 0 skipped
+*/
+
+/*curr
+PASS   : TestStringBits::initTestCase()
+PASS   : TestStringBits::simple()
+PASS   : TestStringBits::dquoted()
+PASS   : TestStringBits::squoted()
+PASS   : TestStringBits::descaped()
+FAIL!  : TestStringBits::sescaped() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 3
+   Expected (out) size: 2
+   Loc: [o/../TestStringBits.h(65)]
+PASS   : TestStringBits::dnested()
+PASS   : TestStringBits::snested()
+PASS   : TestStringBits::snested2()
+PASS   : TestStringBits::qquoted()
+FAIL!  : TestStringBits::multispace() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 5
+   Expected (out) size: 6
+   Loc: [o/../TestStringBits.h(100)]
+PASS   : TestStringBits::qcommas()
+PASS   : TestStringBits::cleanupTestCase()
+Totals: 11 passed, 2 failed, 0 skipped
+*/
--- a/base/test/main.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/test/main.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -13,6 +13,8 @@
 
 #include "TestRangeMapper.h"
 #include "TestPitch.h"
+#include "TestRealTime.h"
+#include "TestStringBits.h"
 
 #include <QtTest>
 
@@ -36,6 +38,16 @@
 	if (QTest::qExec(&t, argc, argv) == 0) ++good;
 	else ++bad;
     }
+    {
+	TestRealTime t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+    {
+	TestStringBits t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
 
     if (bad > 0) {
 	cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl;
--- a/base/test/test.pro	Mon Nov 10 09:19:49 2014 +0000
+++ b/base/test/test.pro	Mon Mar 23 10:04:48 2015 +0000
@@ -49,7 +49,7 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-HEADERS += TestRangeMapper.h TestPitch.h
+HEADERS += TestRangeMapper.h TestPitch.h TestRealTime.h TestStringBits.h
 SOURCES += main.cpp
 
 win* {
--- a/data/fileio/AudioFileReader.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/AudioFileReader.h	Mon Mar 23 10:04:48 2015 +0000
@@ -62,6 +62,16 @@
      */
     virtual QString getMaker() const { return ""; }
 
+    /**
+     * Return the local file path of the audio data. This is the
+     * location most likely to contain readable audio data: it may be
+     * in a different place or format from the originally specified
+     * location, for example if the file has been retrieved and
+     * decoded. In some cases there may be no local file path, and
+     * this will return "" if there is none.
+     */
+    virtual QString getLocalFilename() const { return ""; }
+    
     typedef std::map<QString, QString> TagMap;
     virtual TagMap getTags() const { return TagMap(); }
 
--- a/data/fileio/CSVFileReader.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/CSVFileReader.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -26,6 +26,7 @@
 #include "DataFileReaderFactory.h"
 
 #include <QFile>
+#include <QFileInfo>
 #include <QString>
 #include <QRegExp>
 #include <QStringList>
@@ -37,42 +38,55 @@
 CSVFileReader::CSVFileReader(QString path, CSVFormat format,
                              int mainModelSampleRate) :
     m_format(format),
-    m_file(0),
+    m_device(0),
+    m_ownDevice(true),
     m_warnings(0),
     m_mainModelSampleRate(mainModelSampleRate)
 {
-    m_file = new QFile(path);
+    QFile *file = new QFile(path);
     bool good = false;
     
-    if (!m_file->exists()) {
+    if (!file->exists()) {
 	m_error = QFile::tr("File \"%1\" does not exist").arg(path);
-    } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) {
+    } else if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) {
 	m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
     } else {
 	good = true;
     }
 
-    if (!good) {
-	delete m_file;
-	m_file = 0;
+    if (good) {
+        m_device = file;
+        m_filename = QFileInfo(path).fileName();
+    } else {
+	delete file;
     }
 }
 
+CSVFileReader::CSVFileReader(QIODevice *device, CSVFormat format,
+                             int mainModelSampleRate) :
+    m_format(format),
+    m_device(device),
+    m_ownDevice(false),
+    m_warnings(0),
+    m_mainModelSampleRate(mainModelSampleRate)
+{
+}
+
 CSVFileReader::~CSVFileReader()
 {
-    SVDEBUG << "CSVFileReader::~CSVFileReader: file is " << m_file << endl;
+    SVDEBUG << "CSVFileReader::~CSVFileReader: device is " << m_device << endl;
 
-    if (m_file) {
-        SVDEBUG << "CSVFileReader::CSVFileReader: Closing file" << endl;
-        m_file->close();
+    if (m_device && m_ownDevice) {
+        SVDEBUG << "CSVFileReader::CSVFileReader: Closing device" << endl;
+        m_device->close();
+        delete m_device;
     }
-    delete m_file;
 }
 
 bool
 CSVFileReader::isOK() const
 {
-    return (m_file != 0);
+    return (m_device != 0);
 }
 
 QString
@@ -136,7 +150,7 @@
 Model *
 CSVFileReader::load() const
 {
-    if (!m_file) return 0;
+    if (!m_device) return 0;
 
     CSVFormat::ModelType modelType = m_format.getModelType();
     CSVFormat::TimingType timingType = m_format.getTimingType();
@@ -168,8 +182,7 @@
     EditableDenseThreeDimensionalModel *model3 = 0;
     Model *model = 0;
 
-    QTextStream in(m_file);
-    in.seek(0);
+    QTextStream in(m_device);
 
     unsigned int warnings = 0, warnLimit = 10;
     unsigned int lineno = 0;
@@ -215,7 +228,7 @@
         for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
-
+            
             if (line.startsWith("#")) continue;
 
             QStringList list = StringBits::split(line, separator, allowQuoting);
@@ -252,6 +265,12 @@
                     model = model3;
                     break;
                 }
+
+                if (model) {
+                    if (m_filename != "") {
+                        model->setObjectName(m_filename);
+                    }
+                }
             }
 
             float value = 0.f;
--- a/data/fileio/CSVFileReader.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/CSVFileReader.h	Mon Mar 23 10:04:48 2015 +0000
@@ -28,16 +28,32 @@
 class CSVFileReader : public DataFileReader
 {
 public:
+    /**
+     * Construct a CSVFileReader to read the CSV file at the given
+     * path, with the given format.
+     */
     CSVFileReader(QString path, CSVFormat format, int mainModelSampleRate);
+
+    /**
+     * Construct a CSVFileReader to read from the given
+     * QIODevice. Caller retains ownership of the QIODevice: the
+     * CSVFileReader will not close or delete it and it must outlive
+     * the CSVFileReader.
+     */
+    CSVFileReader(QIODevice *device, CSVFormat format, int mainModelSampleRate);
+
     virtual ~CSVFileReader();
 
     virtual bool isOK() const;
     virtual QString getError() const;
+
     virtual Model *load() const;
 
 protected:
     CSVFormat m_format;
-    QFile *m_file;
+    QIODevice *m_device;
+    bool m_ownDevice;
+    QString m_filename;
     QString m_error;
     mutable int m_warnings;
     int m_mainModelSampleRate;
--- a/data/fileio/CSVFormat.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/CSVFormat.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -110,7 +110,7 @@
     // something that indicates otherwise:
 
     ColumnQualities defaultQualities =
-        ColumnNumeric | ColumnIntegral | ColumnIncreasing;
+        ColumnNumeric | ColumnIntegral | ColumnIncreasing | ColumnNearEmpty;
     
     for (int i = 0; i < cols; ++i) {
 	    
@@ -128,7 +128,12 @@
         bool integral   = (qualities & ColumnIntegral);
         bool increasing = (qualities & ColumnIncreasing);
         bool large      = (qualities & ColumnLarge); // this one defaults to off
+        bool emptyish   = (qualities & ColumnNearEmpty);
 
+        if (lineno > 1 && s.trimmed() != "") {
+            emptyish = false;
+        }
+        
         float value = 0.f;
 
         //!!! how to take into account headers?
@@ -166,7 +171,8 @@
             (numeric    ? ColumnNumeric : 0) |
             (integral   ? ColumnIntegral : 0) |
             (increasing ? ColumnIncreasing : 0) |
-            (large      ? ColumnLarge : 0);
+            (large      ? ColumnLarge : 0) |
+            (emptyish   ? ColumnNearEmpty : 0);
     }
 
     if (lineno < 10) {
@@ -190,11 +196,31 @@
     m_timeUnits = CSVFormat::TimeWindows;
 	
     int timingColumnCount = 0;
+
+    // if our first column has zero or one entries in it and the rest
+    // have more, then we'll default to ignoring the first column and
+    // counting the next one as primary. (e.g. Sonic Annotator output
+    // with filename at start of first column.)
+
+    int primaryColumnNo = 0;
+
+    if (m_columnCount >= 2) {
+        if ( (m_columnQualities[0] & ColumnNearEmpty) &&
+            !(m_columnQualities[1] & ColumnNearEmpty)) {
+            primaryColumnNo = 1;
+        }
+    }
     
     for (int i = 0; i < m_columnCount; ++i) {
         
         ColumnPurpose purpose = ColumnUnknown;
-        bool primary = (i == 0);
+
+        if (i < primaryColumnNo) {
+            setColumnPurpose(i, purpose);
+            continue;
+        }
+        
+        bool primary = (i == primaryColumnNo);
 
         ColumnQualities qualities = m_columnQualities[i];
 
--- a/data/fileio/CSVFormat.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/CSVFormat.h	Mon Mar 23 10:04:48 2015 +0000
@@ -53,10 +53,11 @@
     };
 
     enum ColumnQuality {
-        ColumnNumeric    = 0x1,
-        ColumnIntegral   = 0x2,
-        ColumnIncreasing = 0x4,
-        ColumnLarge      = 0x8
+        ColumnNumeric    = 1,
+        ColumnIntegral   = 2,
+        ColumnIncreasing = 4,
+        ColumnLarge      = 8,
+        ColumnNearEmpty  = 16,
     };
     typedef unsigned int ColumnQualities;
 
@@ -100,8 +101,8 @@
     void setTimingType(TimingType t)      { m_timingType   = t; }
     void setTimeUnits(TimeUnits t)        { m_timeUnits    = t; }
     void setSeparator(QChar s)            { m_separator    = s; }
-    void setSampleRate(int r)          { m_sampleRate   = r; }
-    void setWindowSize(int s)          { m_windowSize   = s; }
+    void setSampleRate(int r)             { m_sampleRate   = r; }
+    void setWindowSize(int s)             { m_windowSize   = s; }
     void setColumnCount(int c)            { m_columnCount  = c; }
     void setAllowQuoting(bool q)          { m_allowQuoting = q; }
 
--- a/data/fileio/CodedAudioFileReader.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/CodedAudioFileReader.h	Mon Mar 23 10:04:48 2015 +0000
@@ -43,6 +43,8 @@
 
     virtual int getNativeRate() const { return m_fileRate; }
 
+    virtual QString getLocalFilename() const { return m_cacheFileName; }
+    
     /// Intermediate cache means all CodedAudioFileReaders are quickly seekable
     virtual bool isQuicklySeekable() const { return true; }
 
--- a/data/fileio/FileSource.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/FileSource.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -32,7 +32,7 @@
 
 #include <unistd.h>
 
-//#define DEBUG_FILE_SOURCE 1
+#define DEBUG_FILE_SOURCE 1
 
 int
 FileSource::m_count = 0;
@@ -51,8 +51,11 @@
 
 #ifdef DEBUG_FILE_SOURCE
 static int extantCount = 0;
+static int threadCount = 0;
 static std::map<QString, int> urlExtantCountMap;
+static QMutex countMutex;
 static void incCount(QString url) {
+    QMutexLocker locker(&countMutex);
     ++extantCount;
     if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
         urlExtantCountMap[url] = 1;
@@ -62,10 +65,26 @@
     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
 }
 static void decCount(QString url) {
+    QMutexLocker locker(&countMutex);
     --extantCount;
     --urlExtantCountMap[url];
     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
 }
+void
+FileSource::debugReport()
+{
+    QMutexLocker locker(&countMutex);
+    cerr << "\nFileSource::debugReport: Have " << extantCount << " FileSource object(s) extant across " << threadCount << " thread(s)" << endl;
+    cerr << "URLs by extant count:" << endl;
+    cerr << "Count | URL" << endl;
+    for (std::map<QString, int>::const_iterator i = urlExtantCountMap.begin();
+         i != urlExtantCountMap.end(); ++i) {
+        cerr << i->second << " | " << i->first << endl;
+    }
+    cerr << "FileSource::debugReport done\n" << endl;
+}
+#else
+void FileSource::debugReport() { }
 #endif
 
 static QThreadStorage<QNetworkAccessManager *> nms;
@@ -268,13 +287,6 @@
 void
 FileSource::init()
 {
-    { // check we have a QNetworkAccessManager
-        QMutexLocker locker(&m_mapMutex);
-        if (!nms.hasLocalData()) {
-            nms.setLocalData(new QNetworkAccessManager());
-        }
-    }
-
     if (isResource()) {
 #ifdef DEBUG_FILE_SOURCE
         cerr << "FileSource::init: Is a resource" << endl;
@@ -463,6 +475,16 @@
              QString("%1, */*").arg(m_preferredContentType).toLatin1());
     }
 
+    { // check we have a QNetworkAccessManager
+        QMutexLocker locker(&m_mapMutex);
+        if (!nms.hasLocalData()) {
+#ifdef DEBUG_FILE_SOURCE
+            ++threadCount;
+#endif
+            nms.setLocalData(new QNetworkAccessManager());
+        }
+    }
+
     m_reply = nms.localData()->get(req);
 
     connect(m_reply, SIGNAL(readyRead()),
--- a/data/fileio/FileSource.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/FileSource.h	Mon Mar 23 10:04:48 2015 +0000
@@ -195,6 +195,13 @@
      */
     static bool canHandleScheme(QUrl url);
 
+    /**
+     * Print some stats, if FileSource was compiled with debugging.
+     * It's safe to leave a call to this function in release code, as
+     * long as FileSource itself is compiled with release flags.
+     */
+    static void debugReport();
+    
 signals:
     /**
      * Emitted during URL retrieval, when the retrieval progress
--- a/data/fileio/MIDIFileReader.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/MIDIFileReader.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -36,6 +36,7 @@
 #include "model/NoteModel.h"
 
 #include <QString>
+#include <QFileInfo>
 
 #include <sstream>
 
@@ -932,6 +933,7 @@
     if (!model) {
 	model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
 	model->setValueQuantization(1.0);
+        model->setObjectName(QFileInfo(m_path).fileName());
     }
 
     const MIDITrack &track = m_midiComposition.find(trackToLoad)->second;
--- a/data/fileio/OggVorbisFileReader.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/OggVorbisFileReader.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -173,11 +173,11 @@
 
     if (!reader->m_commentsRead) {
         const FishSoundComment *comment;
-        comment = fish_sound_comment_first_byname(fs, "TITLE");
+        comment = fish_sound_comment_first_byname(fs, (char *)"TITLE");
         if (comment && comment->value) {
             reader->m_title = QString::fromUtf8(comment->value);
         }
-        comment = fish_sound_comment_first_byname(fs, "ARTIST");
+        comment = fish_sound_comment_first_byname(fs, (char *)"ARTIST");
         if (comment && comment->value) {
             reader->m_maker = QString::fromUtf8(comment->value);
         }
--- a/data/fileio/WavFileReader.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/fileio/WavFileReader.h	Mon Mar 23 10:04:48 2015 +0000
@@ -42,6 +42,8 @@
     virtual QString getLocation() const { return m_source.getLocation(); }
     virtual QString getError() const { return m_error; }
 
+    virtual QString getLocalFilename() const { return m_path; }
+    
     virtual bool isQuicklySeekable() const { return m_seekable; }
     
     /** 
--- a/data/model/AggregateWaveModel.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/AggregateWaveModel.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -118,14 +118,21 @@
         }
     }
 
-    int sz = count;
-
+    int longest = 0;
+    
     for (int c = ch0; c <= ch1; ++c) {
-        int szHere = 
+        int here = 
             m_components[c].model->getData(m_components[c].channel,
                                            start, count,
                                            readbuf);
-        if (szHere < sz) sz = szHere;
+        if (here > longest) {
+            longest = here;
+        }
+        if (here < count) {
+            for (int i = here; i < count; ++i) {
+                readbuf[i] = 0.f;
+            }
+        }
         if (mixing) {
             for (int i = 0; i < count; ++i) {
                 buffer[i] += readbuf[i];
@@ -134,7 +141,7 @@
     }
 
     if (mixing) delete[] readbuf;
-    return sz;
+    return longest;
 }
          
 int
@@ -157,14 +164,21 @@
         }
     }
 
-    int sz = count;
+    int longest = 0;
     
     for (int c = ch0; c <= ch1; ++c) {
-        int szHere = 
+        int here = 
             m_components[c].model->getData(m_components[c].channel,
                                            start, count,
                                            readbuf);
-        if (szHere < sz) sz = szHere;
+        if (here > longest) {
+            longest = here;
+        }
+        if (here < count) {
+            for (int i = here; i < count; ++i) {
+                readbuf[i] = 0.;
+            }
+        }
         if (mixing) {
             for (int i = 0; i < count; ++i) {
                 buffer[i] += readbuf[i];
@@ -173,7 +187,7 @@
     }
     
     if (mixing) delete[] readbuf;
-    return sz;
+    return longest;
 }
 
 int
--- a/data/model/AlignmentModel.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/AlignmentModel.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -112,10 +112,17 @@
         if (completion) *completion = 0;
         return false;
     }
-    if (m_pathComplete || !m_rawPath) {
+    if (m_pathComplete) {
         if (completion) *completion = 100;
         return true;
     }
+    if (!m_rawPath) {
+        // lack of raw path could mean path is complete (in which case
+        // m_pathComplete true above) or else no alignment has been
+        // set at all yet (this case)
+        if (completion) *completion = 0;
+        return false;
+    }
     return m_rawPath->isReady(completion);
 }
 
@@ -347,6 +354,31 @@
 }
 
 void
+AlignmentModel::setPathFrom(SparseTimeValueModel *rawpath)
+{
+    if (m_rawPath) m_rawPath->aboutToDelete();
+    delete m_rawPath;
+
+    m_rawPath = rawpath;
+
+    connect(m_rawPath, SIGNAL(modelChanged()),
+            this, SLOT(pathChanged()));
+
+    connect(m_rawPath, SIGNAL(modelChangedWithin(int, int)),
+            this, SLOT(pathChangedWithin(int, int)));
+        
+    connect(m_rawPath, SIGNAL(completionChanged()),
+            this, SLOT(pathCompletionChanged()));
+    
+    constructPath();
+    constructReversePath();
+
+    if (m_rawPath->isReady()) {
+        pathCompletionChanged();
+    }        
+}
+
+void
 AlignmentModel::setPath(PathModel *path)
 {
     if (m_path) m_path->aboutToDelete();
--- a/data/model/AlignmentModel.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/AlignmentModel.h	Mon Mar 23 10:04:48 2015 +0000
@@ -32,7 +32,7 @@
 public:
     AlignmentModel(Model *reference,
                    Model *aligned,
-                   Model *inputModel, // probably an AggregateWaveModel; I take ownership
+                   Model *inputModel, // probably an AggregateWaveModel; may be null; I take ownership
                    SparseTimeValueModel *path); // I take ownership
     ~AlignmentModel();
 
@@ -52,6 +52,7 @@
     int toReference(int frame) const;
     int fromReference(int frame) const;
 
+    void setPathFrom(SparseTimeValueModel *rawpath);
     void setPath(PathModel *path);
 
     virtual void toXml(QTextStream &stream,
--- a/data/model/Model.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/Model.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -85,9 +85,13 @@
         m_alignment->aboutToDelete();
         delete m_alignment;
     }
+    
     m_alignment = alignment;
-    connect(m_alignment, SIGNAL(completionChanged()),
-            this, SIGNAL(alignmentCompletionChanged()));
+
+    if (m_alignment) {
+        connect(m_alignment, SIGNAL(completionChanged()),
+                this, SIGNAL(alignmentCompletionChanged()));
+    }
 }
 
 const AlignmentModel *
--- a/data/model/WaveFileModel.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/WaveFileModel.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -183,6 +183,13 @@
     if (m_reader) return m_reader->getLocation();
     return "";
 }
+
+QString
+WaveFileModel::getLocalFilename() const
+{
+    if (m_reader) return m_reader->getLocalFilename();
+    return "";
+}
     
 int
 WaveFileModel::getData(int channel, int start, int count,
--- a/data/model/WaveFileModel.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/data/model/WaveFileModel.h	Mon Mar 23 10:04:48 2015 +0000
@@ -52,6 +52,8 @@
     QString getMaker() const;
     QString getLocation() const;
 
+    QString getLocalFilename() const;
+
     virtual Model *clone() const;
 
     float getValueMinimum() const { return -1.0f; }
--- a/rdf/RDFFeatureWriter.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/rdf/RDFFeatureWriter.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -22,6 +22,7 @@
 #include "PluginRDFIndexer.h"
 
 #include <QTextStream>
+#include <QTextCodec>
 #include <QUrl>
 #include <QFileInfo>
 #include <QRegExp>
@@ -160,7 +161,8 @@
     // Need to select appropriate output file for our track/transform
     // combination
 
-    QTextStream *stream = getOutputStream(trackId, transform.getIdentifier());
+    QTextStream *stream = getOutputStream(trackId, transform.getIdentifier(),
+                                          QTextCodec::codecForName("UTF-8"));
     if (!stream) {
         throw FailedToOpenOutputStream(trackId, transform.getIdentifier());
     }
--- a/transform/CSVFeatureWriter.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/transform/CSVFeatureWriter.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -23,6 +23,7 @@
 
 #include <QRegExp>
 #include <QTextStream>
+#include <QTextCodec>
 
 using namespace std;
 using namespace Vamp;
@@ -123,7 +124,9 @@
     // Select appropriate output file for our track/transform
     // combination
 
-    QTextStream *sptr = getOutputStream(trackId, transformId);
+    QTextStream *sptr = getOutputStream(trackId,
+                                        transformId,
+                                        QTextCodec::codecForName("UTF-8"));
     if (!sptr) {
         throw FailedToOpenOutputStream(trackId, transformId);
     }
@@ -169,7 +172,9 @@
          i != m_pending.end(); ++i) {
         DataId tt = i->first;
         Plugin::Feature f = i->second;
-        QTextStream *sptr = getOutputStream(tt.first, tt.second.getIdentifier());
+        QTextStream *sptr = getOutputStream(tt.first,
+                                            tt.second.getIdentifier(),
+                                            QTextCodec::codecForName("UTF-8"));
         if (!sptr) {
             throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier());
         }
--- a/transform/FeatureExtractionModelTransformer.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -679,11 +679,17 @@
         if (frequencyDomain) {
             for (int ch = 0; ch < channelCount; ++ch) {
                 int column = (blockFrame - startFrame) / stepSize;
-                fftModels[ch]->getValuesAt(column, reals, imaginaries);
-                for (int i = 0; i <= blockSize/2; ++i) {
-                    buffers[ch][i*2] = reals[i];
-                    buffers[ch][i*2+1] = imaginaries[i];
-                }
+                if (fftModels[ch]->getValuesAt(column, reals, imaginaries)) {
+                    for (int i = 0; i <= blockSize/2; ++i) {
+                        buffers[ch][i*2] = reals[i];
+                        buffers[ch][i*2+1] = imaginaries[i];
+                    }
+                } else {
+                    for (int i = 0; i <= blockSize/2; ++i) {
+                        buffers[ch][i*2] = 0.f;
+                        buffers[ch][i*2+1] = 0.f;
+                    }
+                }                    
                 error = fftModels[ch]->getError();
                 if (error != "") {
                     cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
--- a/transform/FileFeatureWriter.cpp	Mon Nov 10 09:19:49 2014 +0000
+++ b/transform/FileFeatureWriter.cpp	Mon Mar 23 10:04:48 2015 +0000
@@ -315,7 +315,8 @@
 
 
 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
-                                               TransformId transformId)
+                                                TransformId transformId,
+                                                QTextCodec *codec)
 {
     QFile *file = getOutputFile(trackId, transformId);
     if (!file && !m_stdout) {
@@ -328,6 +329,7 @@
         } else {
             m_streams[file] = new QTextStream(file);
         }
+        m_streams[file]->setCodec(codec);
     }
 
     QTextStream *stream = m_streams[file];
--- a/transform/FileFeatureWriter.h	Mon Nov 10 09:19:49 2014 +0000
+++ b/transform/FileFeatureWriter.h	Mon Mar 23 10:04:48 2015 +0000
@@ -32,6 +32,7 @@
 using std::pair;
 
 class QTextStream;
+class QTextCodec;
 class QFile;
 
 class FileFeatureWriter : public FeatureWriter
@@ -55,7 +56,7 @@
     };
 
     FileFeatureWriter(int support, QString extension);
-    QTextStream *getOutputStream(QString, TransformId);
+    QTextStream *getOutputStream(QString, TransformId, QTextCodec *);
 
     typedef pair<QString, TransformId> TrackTransformPair;
     typedef map<TrackTransformPair, QString> FileNameMap;