changeset 548:baa11365ebdd bqaudioio

Merge from branch bqresample
author Chris Cannam
date Wed, 07 Dec 2016 11:51:42 +0000
parents 82d7e5cf7517 (current diff) c4391f6c7484 (diff)
children ec189ad4d38f
files framework/MainWindowBase.cpp
diffstat 18 files changed, 1450 insertions(+), 770 deletions(-) [+]
line wrap: on
line diff
--- a/acinclude.m4	Wed Dec 07 11:50:54 2016 +0000
+++ b/acinclude.m4	Wed Dec 07 11:51:42 2016 +0000
@@ -69,6 +69,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
@@ -78,6 +81,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH)
 fi
 if test x$QMAKE = x ; then
--- a/audio/AudioCallbackPlaySource.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/audio/AudioCallbackPlaySource.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -23,6 +23,7 @@
 #include "base/Preferences.h"
 #include "data/model/DenseTimeValueModel.h"
 #include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
 #include "plugin/RealTimePluginInstance.h"
 
@@ -77,9 +78,7 @@
     m_stretcherInputs(0),
     m_stretcherInputSizes(0),
     m_fillThread(0),
-    m_converter(0),
-    m_crapConverter(0),
-    m_resampleQuality(Preferences::getInstance()->getResampleQuality())
+    m_resampler(0)
 {
     m_viewManager->setAudioPlaySource(this);
 
@@ -161,8 +160,8 @@
     bool buffersChanged = false, srChanged = false;
 
     int modelChannels = 1;
-    DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
-    if (dtvm) modelChannels = dtvm->getChannelCount();
+    ReadOnlyWaveFileModel *rowfm = qobject_cast<ReadOnlyWaveFileModel *>(model);
+    if (rowfm) modelChannels = rowfm->getChannelCount();
     if (modelChannels > m_sourceChannelCount) {
 	m_sourceChannelCount = modelChannels;
     }
@@ -178,24 +177,26 @@
 
     } else if (model->getSampleRate() != m_sourceSampleRate) {
 
-        // If this is a dense time-value model and we have no other, we
-        // can just switch to this model's sample rate
+        // If this is a read-only wave file model and we have no
+        // other, we can just switch to this model's sample rate
 
-        if (dtvm) {
+        if (rowfm) {
 
             bool conflicting = false;
 
             for (std::set<Model *>::const_iterator i = m_models.begin();
                  i != m_models.end(); ++i) {
-                // Only wave file models can be considered conflicting --
-                // writable wave file models are derived and we shouldn't
-                // take their rates into account.  Also, don't give any
-                // particular weight to a file that's already playing at
-                // the wrong rate anyway
-                WaveFileModel *wfm = dynamic_cast<WaveFileModel *>(*i);
-                if (wfm && wfm != dtvm &&
-                    wfm->getSampleRate() != model->getSampleRate() &&
-                    wfm->getSampleRate() == m_sourceSampleRate) {
+                // Only read-only wave file models should be
+                // considered conflicting -- writable wave file models
+                // are derived and we shouldn't take their rates into
+                // account.  Also, don't give any particular weight to
+                // a file that's already playing at the wrong rate
+                // anyway
+                ReadOnlyWaveFileModel *other =
+                    qobject_cast<ReadOnlyWaveFileModel *>(*i);
+                if (other && other != rowfm &&
+                    other->getSampleRate() != model->getSampleRate() &&
+                    other->getSampleRate() == m_sourceSampleRate) {
                     SVDEBUG << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << *i << " found" << endl;
                     conflicting = true;
                     break;
@@ -229,11 +230,12 @@
     }
 
     if (buffersChanged || srChanged) {
-	if (m_converter) {
-	    src_delete(m_converter);
-            src_delete(m_crapConverter);
-	    m_converter = 0;
-            m_crapConverter = 0;
+	if (m_resampler) {
+#ifdef DEBUG_AUDIO_PLAY_SOURCE
+            cerr << "AudioCallbackPlaySource::addModel: Buffers or sample rate changed, deleting existing resampler" << endl;
+#endif
+            delete m_resampler;
+	    m_resampler = 0;
 	}
     }
 
@@ -241,6 +243,8 @@
 
     m_mutex.unlock();
 
+    initialiseResampler();
+    
     m_audioGenerator->setTargetChannelCount(getTargetChannelCount());
 
     if (!m_fillThread) {
@@ -297,11 +301,12 @@
     m_models.erase(model);
 
     if (m_models.empty()) {
-	if (m_converter) {
-	    src_delete(m_converter);
-            src_delete(m_crapConverter);
-	    m_converter = 0;
-            m_crapConverter = 0;
+	if (m_resampler) {
+#ifdef DEBUG_AUDIO_PLAY_SOURCE
+            cerr << "AudioCallbackPlaySource::removeModel: No models left, deleting resampler" << endl;
+#endif
+	    delete m_resampler;
+	    m_resampler = 0;
 	}
 	m_sourceSampleRate = 0;
     }
@@ -339,11 +344,12 @@
 
     m_models.clear();
 
-    if (m_converter) {
-	src_delete(m_converter);
-        src_delete(m_crapConverter);
-	m_converter = 0;
-        m_crapConverter = 0;
+    if (m_resampler) {
+#ifdef DEBUG_AUDIO_PLAY_SOURCE
+        cerr << "AudioCallbackPlaySource::clearModels: Deleting resampler" << endl;
+#endif
+	delete m_resampler;
+	m_resampler = 0;
     }
 
     m_lastModelEndFrame = 0;
@@ -407,6 +413,8 @@
 void
 AudioCallbackPlaySource::play(sv_frame_t startFrame)
 {
+    if (!m_target) return;
+    
     if (!m_sourceSampleRate) {
         cerr << "AudioCallbackPlaySource::play: No source sample rate available, not playing" << endl;
         return;
@@ -464,8 +472,9 @@
             if (rb) rb->reset();
         }
     }
-    if (m_converter) src_reset(m_converter);
-    if (m_crapConverter) src_reset(m_crapConverter);
+    if (m_resampler) {
+        m_resampler->reset();
+    }
 
     m_mutex.unlock();
 
@@ -551,9 +560,6 @@
 void
 AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n)
 {
-    if (n == "Resample Quality") {
-        setResampleQuality(Preferences::getInstance()->getResampleQuality());
-    }
 }
 
 void
@@ -941,7 +947,7 @@
     bool first = (m_targetSampleRate == 0);
 
     m_targetSampleRate = sr;
-    initialiseConverter();
+    initialiseResampler();
 
     if (first && (m_stretchRatio != 1.f)) {
         // couldn't create a stretcher before because we had no sample
@@ -951,81 +957,37 @@
 }
 
 void
-AudioCallbackPlaySource::initialiseConverter()
+AudioCallbackPlaySource::initialiseResampler()
 {
     m_mutex.lock();
 
-    if (m_converter) {
-        src_delete(m_converter);
-        src_delete(m_crapConverter);
-        m_converter = 0;
-        m_crapConverter = 0;
+#ifdef DEBUG_AUDIO_PLAY_SOURCE
+    cerr << "AudioCallbackPlaySource::initialiseResampler(): from "
+         << getSourceSampleRate() << " to " << getTargetSampleRate() << endl;
+#endif
+    
+    if (m_resampler) {
+        delete m_resampler;
+        m_resampler = 0;
     }
 
     if (getSourceSampleRate() != getTargetSampleRate()) {
 
-	int err = 0;
+        m_resampler = new breakfastquay::Resampler
+            (breakfastquay::Resampler::FastestTolerable,
+             getTargetChannelCount());
 
-	m_converter = src_new(m_resampleQuality == 2 ? SRC_SINC_BEST_QUALITY :
-                              m_resampleQuality == 1 ? SRC_SINC_MEDIUM_QUALITY :
-                              m_resampleQuality == 0 ? SRC_SINC_FASTEST :
-                                                       SRC_SINC_MEDIUM_QUALITY,
-			      getTargetChannelCount(), &err);
+        m_mutex.unlock();
 
-        if (m_converter) {
-            m_crapConverter = src_new(SRC_LINEAR,
-                                      getTargetChannelCount(),
-                                      &err);
-        }
-
-	if (!m_converter || !m_crapConverter) {
-	    cerr
-		<< "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: "
-		<< src_strerror(err) << endl;
-
-            if (m_converter) {
-                src_delete(m_converter);
-                m_converter = 0;
-            } 
-
-            if (m_crapConverter) {
-                src_delete(m_crapConverter);
-                m_crapConverter = 0;
-            }
-
-            m_mutex.unlock();
-
-            emit sampleRateMismatch(getSourceSampleRate(),
-                                    getTargetSampleRate(),
-                                    false);
-	} else {
-
-            m_mutex.unlock();
-
-            emit sampleRateMismatch(getSourceSampleRate(),
-                                    getTargetSampleRate(),
-                                    true);
-        }
+        emit sampleRateMismatch(getSourceSampleRate(),
+                                getTargetSampleRate(),
+                                true);
     } else {
         m_mutex.unlock();
     }
 }
 
 void
-AudioCallbackPlaySource::setResampleQuality(int q)
-{
-    if (q == m_resampleQuality) return;
-    m_resampleQuality = q;
-
-#ifdef DEBUG_AUDIO_PLAY_SOURCE
-    SVDEBUG << "AudioCallbackPlaySource::setResampleQuality: setting to "
-              << m_resampleQuality << endl;
-#endif
-
-    initialiseConverter();
-}
-
-void
 AudioCallbackPlaySource::setAuditioningEffect(Auditionable *a)
 {
     RealTimePluginInstance *plugin = dynamic_cast<RealTimePluginInstance *>(a);
@@ -1393,6 +1355,9 @@
         return false;
     }
 
+    // space is now the number of samples that can be written on each
+    // channel's write ringbuffer
+    
     sv_frame_t f = m_writeBufferFill;
 	
     bool readWriteEqual = (m_readBuffers == m_writeBuffers);
@@ -1430,15 +1395,11 @@
 
     sv_frame_t generatorBlockSize = m_audioGenerator->getBlockSize();
 
-    if (resample && !m_converter) {
-	static bool warned = false;
-	if (!warned) {
-	    cerr << "WARNING: sample rates differ, but no converter available!" << endl;
-	    warned = true;
-	}
+    if (resample && !m_resampler) {
+        throw std::logic_error("Sample rates differ, but no resampler available!");
     }
 
-    if (resample && m_converter) {
+    if (resample && m_resampler) {
 
 	double ratio =
 	    double(getTargetSampleRate()) / double(getSourceSampleRate());
@@ -1490,7 +1451,10 @@
 		intlv[channels * i + c] = sample;
 	    }
 	}
-		
+
+        sv_frame_t toCopy = m_resampler->resampleInterleaved
+            (intlv, srcout, got, ratio, false);
+        
 	SRC_DATA data;
 	data.data_in = intlv;
 	data.data_out = srcout;
@@ -1499,16 +1463,7 @@
 	data.src_ratio = ratio;
 	data.end_of_input = 0;
 	
-	int err = 0;
-
-        if (m_timeStretcher && m_timeStretcher->getTimeRatio() < 0.4) {
-#ifdef DEBUG_AUDIO_PLAY_SOURCE
-            cout << "Using crappy converter" << endl;
-#endif
-            err = src_process(m_crapConverter, &data);
-        } else {
-            err = src_process(m_converter, &data);
-        }
+	int err = src_process(m_converter, &data);
 
 	sv_frame_t toCopy = sv_frame_t(double(got) * ratio + 0.1);
 
--- a/audio/AudioCallbackPlaySource.h	Wed Dec 07 11:50:54 2016 +0000
+++ b/audio/AudioCallbackPlaySource.h	Wed Dec 07 11:51:42 2016 +0000
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _AUDIO_CALLBACK_PLAY_SOURCE_H_
-#define _AUDIO_CALLBACK_PLAY_SOURCE_H_
+#ifndef AUDIO_CALLBACK_PLAY_SOURCE_H
+#define AUDIO_CALLBACK_PLAY_SOURCE_H
 
 #include "base/RingBuffer.h"
 #include "base/AudioPlaySource.h"
@@ -39,6 +39,10 @@
     class RubberBandStretcher;
 }
 
+namespace breakfastquay {
+    class Resampler;
+}
+
 class Model;
 class ViewManagerBase;
 class AudioGenerator;
@@ -116,8 +120,7 @@
     virtual sv_frame_t getPlayEndFrame() { return m_lastModelEndFrame; }
 
     /**
-     * Set the playback target.  This should be called by the target
-     * class.
+     * Set the playback target.
      */
     virtual void setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *);
 
@@ -227,12 +230,6 @@
     void setTimeStretch(double factor);
 
     /**
-     * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is
-     * highest quality.
-     */
-    void setResampleQuality(int q);
-
-    /**
      * Set a single real-time plugin as a processing effect for
      * auditioning during playback.
      *
@@ -396,10 +393,8 @@
     QMutex m_mutex;
     QWaitCondition m_condition;
     FillThread *m_fillThread;
-    SRC_STATE *m_converter;
-    SRC_STATE *m_crapConverter; // for use when playing very fast
-    int m_resampleQuality;
-    void initialiseConverter();
+    breakfastquay::Resampler *m_resampler;
+    void initialiseResampler();
 };
 
 #endif
--- a/audio/AudioGenerator.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/audio/AudioGenerator.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -439,17 +439,20 @@
     sv_frame_t got = 0;
 
     if (startFrame >= fadeIn/2) {
-        got = dtvm->getMultiChannelData(0, modelChannels - 1,
-                                        startFrame - fadeIn/2,
-                                        frames + fadeOut/2 + fadeIn/2,
-                                        m_channelBuffer);
+
+        auto data = dtvm->getMultiChannelData(0, modelChannels - 1,
+                                              startFrame - fadeIn/2,
+                                              frames + fadeOut/2 + fadeIn/2);
+
+        for (int c = 0; c < modelChannels; ++c) {
+            copy(data[c].begin(), data[c].end(), m_channelBuffer[c]);
+        }
+
+        got = data[0].size();
+
     } else {
         sv_frame_t missing = fadeIn/2 - startFrame;
 
-        for (int c = 0; c < modelChannels; ++c) {
-            m_channelBuffer[c] += missing;
-        }
-
         if (missing > 0) {
             cerr << "note: channelBufSiz = " << m_channelBufSiz
                  << ", frames + fadeOut/2 = " << frames + fadeOut/2 
@@ -457,16 +460,14 @@
                  << ", missing = " << missing << endl;
         }
 
-        got = dtvm->getMultiChannelData(0, modelChannels - 1,
-                                        startFrame,
-                                        frames + fadeOut/2,
-                                        m_channelBuffer);
-
+        auto data = dtvm->getMultiChannelData(0, modelChannels - 1,
+                                              startFrame,
+                                              frames + fadeOut/2);
         for (int c = 0; c < modelChannels; ++c) {
-            m_channelBuffer[c] -= missing;
+            copy(data[c].begin(), data[c].end(), m_channelBuffer[c] + missing);
         }
 
-        got += missing;
+        got = data[0].size() + missing;
     }	    
 
     for (int c = 0; c < m_targetChannelCount; ++c) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioRecordTarget.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -0,0 +1,193 @@
+/* -*- 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 "AudioRecordTarget.h"
+
+#include "base/ViewManagerBase.h"
+#include "base/TempDirectory.h"
+
+#include "data/model/WritableWaveFileModel.h"
+
+#include <QDir>
+
+AudioRecordTarget::AudioRecordTarget(ViewManagerBase *manager,
+				     QString clientName) :
+    m_viewManager(manager),
+    m_clientName(clientName.toUtf8().data()),
+    m_recording(false),
+    m_recordSampleRate(44100),
+    m_frameCount(0),
+    m_model(0)
+{
+}
+
+AudioRecordTarget::~AudioRecordTarget()
+{
+    QMutexLocker locker(&m_mutex);
+}
+
+void
+AudioRecordTarget::setSystemRecordBlockSize(int)
+{
+}
+
+void
+AudioRecordTarget::setSystemRecordSampleRate(int n)
+{
+    m_recordSampleRate = n;
+}
+
+void
+AudioRecordTarget::setSystemRecordLatency(int)
+{
+}
+
+void
+AudioRecordTarget::putSamples(int nframes, float **samples)
+{
+    bool secChanged = false;
+    sv_frame_t frameToEmit = 0;
+
+    {
+        QMutexLocker locker(&m_mutex); //!!! bad here
+        if (!m_recording) return;
+
+        m_model->addSamples(samples, nframes);
+
+        sv_frame_t priorFrameCount = m_frameCount;
+        m_frameCount += nframes;
+
+        RealTime priorRT = RealTime::frame2RealTime
+            (priorFrameCount, m_recordSampleRate);
+        RealTime postRT = RealTime::frame2RealTime
+            (m_frameCount, m_recordSampleRate);
+
+        secChanged = (postRT.sec > priorRT.sec);
+        if (secChanged) frameToEmit = m_frameCount;
+    }
+
+    if (secChanged) {
+        emit recordDurationChanged(frameToEmit, m_recordSampleRate);
+    }
+}
+
+void
+AudioRecordTarget::setInputLevels(float, float)
+{
+}
+
+void
+AudioRecordTarget::modelAboutToBeDeleted()
+{
+    QMutexLocker locker(&m_mutex);
+    if (sender() == m_model) {
+        m_model = 0;
+        m_recording = false;
+    }
+}
+
+QString
+AudioRecordTarget::getRecordContainerFolder()
+{
+    QDir parent(TempDirectory::getInstance()->getContainingPath());
+    QString subdirname("recorded");
+
+    if (!parent.mkpath(subdirname)) {
+        cerr << "ERROR: AudioRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+QString
+AudioRecordTarget::getRecordFolder()
+{
+    QDir parent(getRecordContainerFolder());
+    QDateTime now = QDateTime::currentDateTime();
+    QString subdirname = QString("%1").arg(now.toString("yyyyMMdd"));
+
+    if (!parent.mkpath(subdirname)) {
+        cerr << "ERROR: AudioRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+WritableWaveFileModel *
+AudioRecordTarget::startRecording()
+{
+    {
+    QMutexLocker locker(&m_mutex);
+    
+    if (m_recording) {
+        cerr << "WARNING: AudioRecordTarget::startRecording: We are already recording" << endl;
+        return 0;
+    }
+
+    m_model = 0;
+    m_frameCount = 0;
+
+    QString folder = getRecordFolder();
+    if (folder == "") return 0;
+    QDir recordedDir(folder);
+
+    QDateTime now = QDateTime::currentDateTime();
+
+    // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
+    // isn't permitted in filenames on Windows
+    QString filename = QString("recorded-%1.wav")
+        .arg(now.toString("yyyyMMdd-HHmmss-zzz"));
+
+    m_audioFileName = recordedDir.filePath(filename);
+
+    m_model = new WritableWaveFileModel(m_recordSampleRate, 2, m_audioFileName);
+
+    if (!m_model->isOK()) {
+        cerr << "ERROR: AudioRecordTarget::startRecording: Recording failed"
+             << endl;
+        //!!! and throw?
+        delete m_model;
+        m_model = 0;
+        return 0;
+    }
+
+    m_recording = true;
+    }
+
+    emit recordStatusChanged(true);
+    return m_model;
+}
+
+void
+AudioRecordTarget::stopRecording()
+{
+    {
+    QMutexLocker locker(&m_mutex);
+    if (!m_recording) {
+        cerr << "WARNING: AudioRecordTarget::startRecording: Not recording" << endl;
+        return;
+    }
+
+    m_model->writeComplete();
+    m_model = 0;
+    m_recording = false;
+    }
+
+    emit recordStatusChanged(false);
+    emit recordCompleted();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioRecordTarget.h	Wed Dec 07 11:51:42 2016 +0000
@@ -0,0 +1,80 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef AUDIO_RECORD_TARGET_H
+#define AUDIO_RECORD_TARGET_H
+
+#include <bqaudioio/ApplicationRecordTarget.h>
+
+#include <string>
+
+#include <QObject>
+#include <QMutex>
+
+#include "base/BaseTypes.h"
+
+class ViewManagerBase;
+class WritableWaveFileModel;
+
+class AudioRecordTarget : public QObject,
+			  public breakfastquay::ApplicationRecordTarget
+{
+    Q_OBJECT
+
+public:
+    AudioRecordTarget(ViewManagerBase *, QString clientName);
+    virtual ~AudioRecordTarget();
+
+    virtual std::string getClientName() const { return m_clientName; }
+    
+    virtual int getApplicationSampleRate() const { return 0; } // don't care
+    virtual int getApplicationChannelCount() const { return 2; }
+
+    virtual void setSystemRecordBlockSize(int);
+    virtual void setSystemRecordSampleRate(int);
+    virtual void setSystemRecordLatency(int);
+
+    virtual void putSamples(int nframes, float **samples);
+    
+    virtual void setInputLevels(float peakLeft, float peakRight);
+
+    virtual void audioProcessingOverload() { }
+
+    QString getRecordContainerFolder();
+    QString getRecordFolder();
+    
+    bool isRecording() const { return m_recording; }
+    WritableWaveFileModel *startRecording(); // caller takes ownership
+    void stopRecording();
+
+signals:
+    void recordStatusChanged(bool recording);
+    void recordDurationChanged(sv_frame_t, sv_samplerate_t); // emitted occasionally
+    void recordCompleted();
+
+protected slots:
+    void modelAboutToBeDeleted();
+    
+private:
+    ViewManagerBase *m_viewManager;
+    std::string m_clientName;
+    bool m_recording;
+    sv_samplerate_t m_recordSampleRate;
+    sv_frame_t m_frameCount;
+    QString m_audioFileName;
+    WritableWaveFileModel *m_model;
+    QMutex m_mutex;
+};
+
+#endif
--- a/configure	Wed Dec 07 11:50:54 2016 +0000
+++ b/configure	Wed Dec 07 11:51:42 2016 +0000
@@ -646,16 +646,12 @@
 libpulse_CFLAGS
 JACK_LIBS
 JACK_CFLAGS
-portaudio_2_0_LIBS
-portaudio_2_0_CFLAGS
+portaudio_LIBS
+portaudio_CFLAGS
 liblo_LIBS
 liblo_CFLAGS
 rubberband_LIBS
 rubberband_CFLAGS
-vamphostsdk_LIBS
-vamphostsdk_CFLAGS
-vamp_LIBS
-vamp_CFLAGS
 samplerate_LIBS
 samplerate_CFLAGS
 sndfile_LIBS
@@ -756,16 +752,12 @@
 sndfile_LIBS
 samplerate_CFLAGS
 samplerate_LIBS
-vamp_CFLAGS
-vamp_LIBS
-vamphostsdk_CFLAGS
-vamphostsdk_LIBS
 rubberband_CFLAGS
 rubberband_LIBS
 liblo_CFLAGS
 liblo_LIBS
-portaudio_2_0_CFLAGS
-portaudio_2_0_LIBS
+portaudio_CFLAGS
+portaudio_LIBS
 JACK_CFLAGS
 JACK_LIBS
 libpulse_CFLAGS
@@ -1423,12 +1415,6 @@
               C compiler flags for samplerate, overriding pkg-config
   samplerate_LIBS
               linker flags for samplerate, overriding pkg-config
-  vamp_CFLAGS C compiler flags for vamp, overriding pkg-config
-  vamp_LIBS   linker flags for vamp, overriding pkg-config
-  vamphostsdk_CFLAGS
-              C compiler flags for vamphostsdk, overriding pkg-config
-  vamphostsdk_LIBS
-              linker flags for vamphostsdk, overriding pkg-config
   rubberband_CFLAGS
               C compiler flags for rubberband, overriding pkg-config
   rubberband_LIBS
@@ -1436,10 +1422,10 @@
   liblo_CFLAGS
               C compiler flags for liblo, overriding pkg-config
   liblo_LIBS  linker flags for liblo, overriding pkg-config
-  portaudio_2_0_CFLAGS
-              C compiler flags for portaudio_2_0, overriding pkg-config
-  portaudio_2_0_LIBS
-              linker flags for portaudio_2_0, overriding pkg-config
+  portaudio_CFLAGS
+              C compiler flags for portaudio, overriding pkg-config
+  portaudio_LIBS
+              linker flags for portaudio, overriding pkg-config
   JACK_CFLAGS C compiler flags for JACK, overriding pkg-config
   JACK_LIBS   linker flags for JACK, overriding pkg-config
   libpulse_CFLAGS
@@ -4137,6 +4123,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $QTDIR/bin/
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4254,6 +4279,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -5148,18 +5212,18 @@
 fi
 
 
-SV_MODULE_MODULE=vamp
-SV_MODULE_VERSION_TEST="vamp >= 2.1"
-SV_MODULE_HEADER=vamp/vamp.h
-SV_MODULE_LIB=
-SV_MODULE_FUNC=
-SV_MODULE_HAVE=HAVE_$(echo vamp | tr 'a-z' 'A-Z')
+SV_MODULE_MODULE=rubberband
+SV_MODULE_VERSION_TEST="rubberband"
+SV_MODULE_HEADER=rubberband/RubberBandStretcher.h
+SV_MODULE_LIB=rubberband
+SV_MODULE_FUNC=rubberband_new
+SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$vamp_LIBS" ; then
+if test -n "$rubberband_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamp_CFLAGS"
-   LIBS="$LIBS $vamp_LIBS"
+   CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS"
+   LIBS="$LIBS $rubberband_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5168,11 +5232,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamp" >&5
-$as_echo_n "checking for vamp... " >&6; }
-
-if test -n "$vamp_CFLAGS"; then
-    pkg_cv_vamp_CFLAGS="$vamp_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5
+$as_echo_n "checking for rubberband... " >&6; }
+
+if test -n "$rubberband_CFLAGS"; then
+    pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5180,7 +5244,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamp_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5188,8 +5252,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$vamp_LIBS"; then
-    pkg_cv_vamp_LIBS="$vamp_LIBS"
+if test -n "$rubberband_LIBS"; then
+    pkg_cv_rubberband_LIBS="$rubberband_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5197,7 +5261,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamp_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5218,12 +5282,12 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$vamp_PKG_ERRORS" >&5
+	echo "$rubberband_PKG_ERRORS" >&5
 
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
@@ -5233,11 +5297,11 @@
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 else
-	vamp_CFLAGS=$pkg_cv_vamp_CFLAGS
-	vamp_LIBS=$pkg_cv_vamp_LIBS
+	rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS
+	rubberband_LIBS=$pkg_cv_rubberband_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamp_CFLAGS";LIBS="$LIBS $vamp_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
@@ -5299,18 +5363,19 @@
 fi
 
 
-SV_MODULE_MODULE=vamphostsdk
-SV_MODULE_VERSION_TEST="vamp-hostsdk >= 2.5"
-SV_MODULE_HEADER=vamp-hostsdk/PluginLoader.h
-SV_MODULE_LIB=vamp-hostsdk
-SV_MODULE_FUNC=libvamphostsdk_v_2_5_present
-SV_MODULE_HAVE=HAVE_$(echo vamphostsdk | tr 'a-z' 'A-Z')
+
+SV_MODULE_MODULE=liblo
+SV_MODULE_VERSION_TEST=""
+SV_MODULE_HEADER=lo/lo.h
+SV_MODULE_LIB=lo
+SV_MODULE_FUNC=lo_address_new
+SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$vamphostsdk_LIBS" ; then
+if test -n "$liblo_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS"
-   LIBS="$LIBS $vamphostsdk_LIBS"
+   CXXFLAGS="$CXXFLAGS $liblo_CFLAGS"
+   LIBS="$LIBS $liblo_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5319,11 +5384,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamphostsdk" >&5
-$as_echo_n "checking for vamphostsdk... " >&6; }
-
-if test -n "$vamphostsdk_CFLAGS"; then
-    pkg_cv_vamphostsdk_CFLAGS="$vamphostsdk_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5
+$as_echo_n "checking for liblo... " >&6; }
+
+if test -n "$liblo_CFLAGS"; then
+    pkg_cv_liblo_CFLAGS="$liblo_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5331,7 +5396,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5339,8 +5404,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$vamphostsdk_LIBS"; then
-    pkg_cv_vamphostsdk_LIBS="$vamphostsdk_LIBS"
+if test -n "$liblo_LIBS"; then
+    pkg_cv_liblo_LIBS="$liblo_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5348,7 +5413,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5369,40 +5434,42 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$vamphostsdk_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
+	echo "$liblo_PKG_ERRORS" >&5
+
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
+$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 elif test $pkg_failed = untried; then
      	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
 $as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	vamphostsdk_CFLAGS=$pkg_cv_vamphostsdk_CFLAGS
-	vamphostsdk_LIBS=$pkg_cv_vamphostsdk_LIBS
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
+$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
+else
+	liblo_CFLAGS=$pkg_cv_liblo_CFLAGS
+	liblo_LIBS=$pkg_cv_liblo_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS";LIBS="$LIBS $vamphostsdk_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
    as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
 ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
 if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
+  HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED=""
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5
+$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;}
+fi
+
+
+   if test -z "$SV_MODULE_FAILED"; then
+      if test -n "$SV_MODULE_LIB"; then
+           as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
 $as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
 if eval \${$as_ac_Lib+:} false; then :
@@ -5443,25 +5510,27 @@
 if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
   LIBS="$LIBS -l$SV_MODULE_LIB"
 else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
+  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5
+$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;}
+fi
+
+      fi
    fi
 fi
 
 
-SV_MODULE_MODULE=rubberband
-SV_MODULE_VERSION_TEST="rubberband"
-SV_MODULE_HEADER=rubberband/RubberBandStretcher.h
-SV_MODULE_LIB=rubberband
-SV_MODULE_FUNC=rubberband_new
-SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z')
+SV_MODULE_MODULE=portaudio
+SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19"
+SV_MODULE_HEADER=portaudio.h
+SV_MODULE_LIB=portaudio
+SV_MODULE_FUNC=Pa_IsFormatSupported
+SV_MODULE_HAVE=HAVE_$(echo portaudio | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$rubberband_LIBS" ; then
+if test -n "$portaudio_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS"
-   LIBS="$LIBS $rubberband_LIBS"
+   CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS"
+   LIBS="$LIBS $portaudio_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5470,11 +5539,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5
-$as_echo_n "checking for rubberband... " >&6; }
-
-if test -n "$rubberband_CFLAGS"; then
-    pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio" >&5
+$as_echo_n "checking for portaudio... " >&6; }
+
+if test -n "$portaudio_CFLAGS"; then
+    pkg_cv_portaudio_CFLAGS="$portaudio_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5482,7 +5551,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_portaudio_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5490,8 +5559,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$rubberband_LIBS"; then
-    pkg_cv_rubberband_LIBS="$rubberband_LIBS"
+if test -n "$portaudio_LIBS"; then
+    pkg_cv_portaudio_LIBS="$portaudio_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5499,7 +5568,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_portaudio_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5520,164 +5589,12 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        portaudio_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        portaudio_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$rubberband_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS
-	rubberband_LIBS=$pkg_cv_rubberband_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-   fi
-fi
-
-
-
-SV_MODULE_MODULE=liblo
-SV_MODULE_VERSION_TEST=""
-SV_MODULE_HEADER=lo/lo.h
-SV_MODULE_LIB=lo
-SV_MODULE_FUNC=lo_address_new
-SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$liblo_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $liblo_CFLAGS"
-   LIBS="$LIBS $liblo_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5
-$as_echo_n "checking for liblo... " >&6; }
-
-if test -n "$liblo_CFLAGS"; then
-    pkg_cv_liblo_CFLAGS="$liblo_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$liblo_LIBS"; then
-    pkg_cv_liblo_LIBS="$liblo_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$liblo_PKG_ERRORS" >&5
+	echo "$portaudio_PKG_ERRORS" >&5
 
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
@@ -5687,166 +5604,11 @@
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 else
-	liblo_CFLAGS=$pkg_cv_liblo_CFLAGS
-	liblo_LIBS=$pkg_cv_liblo_LIBS
+	portaudio_CFLAGS=$pkg_cv_portaudio_CFLAGS
+	portaudio_LIBS=$pkg_cv_portaudio_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED=""
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;}
-fi
-
-
-   if test -z "$SV_MODULE_FAILED"; then
-      if test -n "$SV_MODULE_LIB"; then
-           as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;}
-fi
-
-      fi
-   fi
-fi
-
-
-SV_MODULE_MODULE=portaudio_2_0
-SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19"
-SV_MODULE_HEADER=portaudio.h
-SV_MODULE_LIB=portaudio
-SV_MODULE_FUNC=Pa_IsFormatSupported
-SV_MODULE_HAVE=HAVE_$(echo portaudio_2_0 | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$portaudio_2_0_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS"
-   LIBS="$LIBS $portaudio_2_0_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio_2_0" >&5
-$as_echo_n "checking for portaudio_2_0... " >&6; }
-
-if test -n "$portaudio_2_0_CFLAGS"; then
-    pkg_cv_portaudio_2_0_CFLAGS="$portaudio_2_0_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_portaudio_2_0_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$portaudio_2_0_LIBS"; then
-    pkg_cv_portaudio_2_0_LIBS="$portaudio_2_0_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_portaudio_2_0_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$portaudio_2_0_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	portaudio_2_0_CFLAGS=$pkg_cv_portaudio_2_0_CFLAGS
-	portaudio_2_0_LIBS=$pkg_cv_portaudio_2_0_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS";LIBS="$LIBS $portaudio_2_0_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS";LIBS="$LIBS $portaudio_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
--- a/configure.ac	Wed Dec 07 11:50:54 2016 +0000
+++ b/configure.ac	Wed Dec 07 11:51:42 2016 +0000
@@ -83,8 +83,6 @@
 SV_MODULE_REQUIRED([fftw3f],[fftw3f >= 3.0.0],[fftw3.h],[fftw3f],[fftwf_execute])
 SV_MODULE_REQUIRED([sndfile],[sndfile >= 1.0.16],[sndfile.h],[sndfile],[sf_open])
 SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new])
-SV_MODULE_REQUIRED([vamp],[vamp >= 2.1],[vamp/vamp.h],[],[])
-SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present])
 SV_MODULE_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new])
 
 SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files.pri	Wed Dec 07 11:51:42 2016 +0000
@@ -0,0 +1,28 @@
+
+SVAPP_HEADERS += \
+           audio/AudioCallbackPlaySource.h \
+           audio/AudioRecordTarget.h \
+           audio/AudioGenerator.h \
+           audio/ClipMixer.h \
+           audio/ContinuousSynth.h \
+           audio/PlaySpeedRangeMapper.h \
+           framework/Align.h \
+	   framework/Document.h \
+           framework/MainWindowBase.h \
+           framework/SVFileReader.h \
+           framework/TransformUserConfigurator.h \
+           framework/VersionTester.h
+
+SVAPP_SOURCES += \
+           audio/AudioCallbackPlaySource.cpp \
+           audio/AudioRecordTarget.cpp \
+           audio/AudioGenerator.cpp \
+           audio/ClipMixer.cpp \
+           audio/ContinuousSynth.cpp \
+           audio/PlaySpeedRangeMapper.cpp \
+	   framework/Align.cpp \
+	   framework/Document.cpp \
+           framework/MainWindowBase.cpp \
+           framework/SVFileReader.cpp \
+           framework/TransformUserConfigurator.cpp \
+           framework/VersionTester.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/framework/Align.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -0,0 +1,310 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "Align.h"
+
+#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
+#include "data/model/AggregateWaveModel.h"
+#include "data/model/RangeSummarisableTimeValueModel.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/AlignmentModel.h"
+
+#include "data/fileio/CSVFileReader.h"
+
+#include "transform/TransformFactory.h"
+#include "transform/ModelTransformerFactory.h"
+#include "transform/FeatureExtractionModelTransformer.h"
+
+#include <QProcess>
+#include <QSettings>
+#include <QApplication>
+
+bool
+Align::alignModel(Model *ref, Model *other)
+{
+    QSettings settings;
+    settings.beginGroup("Preferences");
+    bool useProgram = settings.value("use-external-alignment", false).toBool();
+    QString program = settings.value("external-alignment-program", "").toString();
+    settings.endGroup();
+
+    if (useProgram && (program != "")) {
+        return alignModelViaProgram(ref, other, program);
+    } else {
+        return alignModelViaTransform(ref, other);
+    }
+}
+
+QString
+Align::getAlignmentTransformName()
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    TransformId id =
+        settings.value("transform-id",
+                       "vamp:match-vamp-plugin:match:path").toString();
+    settings.endGroup();
+    return id;
+}
+
+bool
+Align::canAlign() 
+{
+    TransformId id = getAlignmentTransformName();
+    TransformFactory *factory = TransformFactory::getInstance();
+    return factory->haveTransform(id);
+}
+
+bool
+Align::alignModelViaTransform(Model *ref, Model *other)
+{
+    RangeSummarisableTimeValueModel *reference = qobject_cast
+        <RangeSummarisableTimeValueModel *>(ref);
+    
+    RangeSummarisableTimeValueModel *rm = qobject_cast
+        <RangeSummarisableTimeValueModel *>(other);
+
+    if (!reference || !rm) return false; // but this should have been tested already
+   
+    // This involves creating three new models:
+
+    // 1. an AggregateWaveModel to provide the mixdowns of the main
+    // model and the new model in its two channels, as input to the
+    // MATCH plugin
+
+    // 2. a SparseTimeValueModel, which is the model automatically
+    // created by FeatureExtractionPluginTransformer when running the
+    // MATCH plugin (thus containing the alignment path)
+
+    // 3. an AlignmentModel, which stores the path model and carries
+    // out alignment lookups on it.
+
+    // The first two of these are provided as arguments to the
+    // constructor for the third, which takes responsibility for
+    // deleting them.  The AlignmentModel, meanwhile, is passed to the
+    // new model we are aligning, which also takes responsibility for
+    // it.  We should not have to delete any of these new models here.
+
+    AggregateWaveModel::ChannelSpecList components;
+
+    components.push_back(AggregateWaveModel::ModelChannelSpec
+                         (reference, -1));
+
+    components.push_back(AggregateWaveModel::ModelChannelSpec
+                         (rm, -1));
+
+    Model *aggregateModel = new AggregateWaveModel(components);
+    ModelTransformer::Input aggregate(aggregateModel);
+
+    TransformId id = getAlignmentTransformName();
+    
+    TransformFactory *tf = TransformFactory::getInstance();
+
+    Transform transform = tf->getDefaultTransformFor
+        (id, aggregateModel->getSampleRate());
+
+    transform.setStepSize(transform.getBlockSize()/2);
+    transform.setParameter("serialise", 1);
+    transform.setParameter("smooth", 0);
+
+    SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
+
+    ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
+
+    QString message;
+    Model *transformOutput = mtf->transform(transform, aggregate, message);
+
+    if (!transformOutput) {
+        transform.setStepSize(0);
+        transformOutput = mtf->transform(transform, aggregate, message);
+    }
+
+    SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
+        (transformOutput);
+
+    if (!path) {
+        cerr << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
+        delete transformOutput;
+        delete aggregateModel;
+	m_error = message;
+        return false;
+    }
+
+    path->setCompletion(0);
+
+    AlignmentModel *alignmentModel = new AlignmentModel
+        (reference, other, aggregateModel, path);
+
+    connect(alignmentModel, SIGNAL(completionChanged()),
+            this, SLOT(alignmentCompletionChanged()));
+    
+    rm->setAlignment(alignmentModel);
+
+    return true;
+}
+
+void
+Align::alignmentCompletionChanged()
+{
+    AlignmentModel *am = qobject_cast<AlignmentModel *>(sender());
+    if (!am) return;
+    if (am->isReady()) {
+        disconnect(am, SIGNAL(completionChanged()),
+                   this, SLOT(alignmentCompletionChanged()));
+        emit alignmentComplete(am);
+    }
+}
+
+bool
+Align::alignModelViaProgram(Model *ref, Model *other, QString program)
+{
+    WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref);
+    WaveFileModel *rm = qobject_cast<WaveFileModel *>(other);
+
+    if (!reference || !rm) {
+        return false; // but this should have been tested already
+    }
+
+    while (!reference->isReady(0) || !rm->isReady(0)) {
+        qApp->processEvents();
+    }
+    
+    // Run an external program, passing to it paths to the main
+    // model's audio file and the new model's audio file. It returns
+    // the path in CSV form through stdout.
+
+    ReadOnlyWaveFileModel *roref = qobject_cast<ReadOnlyWaveFileModel *>(reference);
+    ReadOnlyWaveFileModel *rorm = qobject_cast<ReadOnlyWaveFileModel *>(rm);
+    if (!roref || !rorm) {
+        cerr << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
+        return false;
+    }
+    
+    QString refPath = roref->getLocalFilename();
+    QString otherPath = rorm->getLocalFilename();
+
+    if (refPath == "" || otherPath == "") {
+	m_error = "Failed to find local filepath for wave-file model";
+	return false;
+    }
+
+    m_error = "";
+    
+    AlignmentModel *alignmentModel = new AlignmentModel(reference, other, 0, 0);
+    rm->setAlignment(alignmentModel);
+
+    QProcess *process = new QProcess;
+    QStringList args;
+    args << refPath << otherPath;
+    
+    connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
+            this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
+
+    m_processModels[process] = alignmentModel;
+    process->start(program, args);
+
+    bool success = process->waitForStarted();
+
+    if (!success) {
+        cerr << "ERROR: Align::alignModelViaProgram: Program did not start"
+             << endl;
+        m_error = "Alignment program could not be started";
+        m_processModels.erase(process);
+        rm->setAlignment(0); // deletes alignmentModel as well
+        delete process;
+    }
+
+    return success;
+}
+
+void
+Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
+{
+    cerr << "Align::alignmentProgramFinished" << endl;
+    
+    QProcess *process = qobject_cast<QProcess *>(sender());
+
+    if (m_processModels.find(process) == m_processModels.end()) {
+        cerr << "ERROR: Align::alignmentProgramFinished: Process " << process
+             << " not found in process model map!" << endl;
+        return;
+    }
+
+    AlignmentModel *alignmentModel = m_processModels[process];
+    
+    if (exitCode == 0 && status == 0) {
+
+	CSVFormat format;
+	format.setModelType(CSVFormat::TwoDimensionalModel);
+	format.setTimingType(CSVFormat::ExplicitTiming);
+	format.setTimeUnits(CSVFormat::TimeSeconds);
+	format.setColumnCount(2);
+        // The output format has time in the reference file first, and
+        // time in the "other" file in the second column. This is a
+        // more natural approach for a command-line alignment tool,
+        // but it's the opposite of what we expect for native
+        // alignment paths, which map from "other" file to
+        // reference. These column purpose settings reflect that.
+	format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
+	format.setColumnPurpose(0, CSVFormat::ColumnValue);
+	format.setAllowQuoting(false);
+	format.setSeparator(',');
+
+	CSVFileReader reader(process, format, alignmentModel->getSampleRate());
+	if (!reader.isOK()) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
+                 << endl;
+	    m_error = QString("Failed to parse output of program: %1")
+		.arg(reader.getError());
+            goto done;
+	}
+
+	Model *csvOutput = reader.load();
+
+	SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
+	if (!path) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
+                 << endl;
+	    m_error = QString("Output of program did not produce sparse time-value model");
+            goto done;
+	}
+
+	if (path->getPoints().empty()) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
+                 << endl;
+	    m_error = QString("Output of alignment program contained no mappings");
+            goto done;
+	}
+
+        cerr << "Align::alignmentProgramFinished: Setting alignment path ("
+             << path->getPoints().size() << " point(s))" << endl;
+        
+        alignmentModel->setPathFrom(path);
+
+        emit alignmentComplete(alignmentModel);
+        
+    } else {
+        cerr << "ERROR: Align::alignmentProgramFinished: Aligner program "
+             << "failed: exit code " << exitCode << ", status " << status
+             << endl;
+	m_error = "Aligner process returned non-zero exit status";
+    }
+
+done:
+    m_processModels.erase(process);
+    delete process;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/framework/Align.h	Wed Dec 07 11:51:42 2016 +0000
@@ -0,0 +1,83 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef ALIGN_H
+#define ALIGN_H
+
+#include <QString>
+#include <QObject>
+#include <QProcess>
+#include <set>
+
+class Model;
+class AlignmentModel;
+
+class Align : public QObject
+{
+    Q_OBJECT
+    
+public:
+    Align() : m_error("") { }
+
+    /**
+     * Align the "other" model to the reference, attaching an
+     * AlignmentModel to it. Alignment is carried out by the method
+     * configured in the user preferences (either a plugin transform
+     * or an external process) and is done asynchronously. 
+     *
+     * A single Align object may carry out many simultanous alignment
+     * calls -- you do not need to create a new Align object each
+     * time, nor to wait for an alignment to be complete before
+     * starting a new one.
+     * 
+     * The Align object must survive after this call, for at least as
+     * long as the alignment takes. The usual expectation is that the
+     * Align object will simply share the process or document
+     * lifespan.
+     */
+    bool alignModel(Model *reference, Model *other); // via user preference
+    
+    bool alignModelViaTransform(Model *reference, Model *other);
+    bool alignModelViaProgram(Model *reference, Model *other, QString program);
+
+    /**
+     * Return true if the alignment facility is available (relevant
+     * plugin installed, etc).
+     */
+    static bool canAlign();
+    
+    QString getError() const { return m_error; }
+
+signals:
+    /**
+     * Emitted when an alignment is successfully completed. The
+     * reference and other models can be queried from the alignment
+     * model.
+     */
+    void alignmentComplete(AlignmentModel *alignment);
+
+private slots:
+    void alignmentCompletionChanged();
+    void alignmentProgramFinished(int, QProcess::ExitStatus);
+    
+private:
+    static QString getAlignmentTransformName();
+    
+    QString m_error;
+    std::map<QProcess *, AlignmentModel *> m_processModels;
+};
+
+#endif
+
--- a/framework/Document.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/Document.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -15,6 +15,8 @@
 
 #include "Document.h"
 
+#include "Align.h"
+
 #include "data/model/WaveFileModel.h"
 #include "data/model/WritableWaveFileModel.h"
 #include "data/model/DenseThreeDimensionalModel.h"
@@ -36,10 +38,8 @@
 #include <iostream>
 #include <typeinfo>
 
-// For alignment:
-#include "data/model/AggregateWaveModel.h"
-#include "data/model/SparseTimeValueModel.h"
 #include "data/model/AlignmentModel.h"
+#include "Align.h"
 
 using std::vector;
 
@@ -49,7 +49,8 @@
 
 Document::Document() :
     m_mainModel(0),
-    m_autoAlignment(false)
+    m_autoAlignment(false),
+    m_align(new Align())
 {
     connect(this,
             SIGNAL(modelAboutToBeDeleted(Model *)),
@@ -60,6 +61,9 @@
             SIGNAL(transformFailed(QString, QString)),
             this,
             SIGNAL(modelGenerationFailed(QString, QString)));
+
+    connect(m_align, SIGNAL(alignmentComplete(AlignmentModel *)),
+            this, SIGNAL(alignmentComplete(AlignmentModel *)));
 }
 
 Document::~Document()
@@ -736,6 +740,10 @@
         // remember is correct for what was actually applied, with the
         // current plugin version.
 
+        //!!! would be nice to short-circuit this -- the version is
+        //!!! static data, shouldn't have to construct a plugin for it
+        //!!! (which may be expensive in Piper-world)
+        
         Transform applied = transforms[j];
         applied.setPluginVersion
             (TransformFactory::getInstance()->
@@ -1038,24 +1046,10 @@
     return (m_models.find(const_cast<Model *>(model)) != m_models.end());
 }
 
-TransformId
-Document::getAlignmentTransformName()
+bool
+Document::canAlign()
 {
-    QSettings settings;
-    settings.beginGroup("Alignment");
-    TransformId id =
-        settings.value("transform-id",
-                       "vamp:match-vamp-plugin:match:path").toString();
-    settings.endGroup();
-    return id;
-}
-
-bool
-Document::canAlign() 
-{
-    TransformId id = getAlignmentTransformName();
-    TransformFactory *factory = TransformFactory::getInstance();
-    return factory->haveTransform(id);
+    return Align::canAlign();
 }
 
 void
@@ -1090,75 +1084,10 @@
         return;
     }
 
-    // This involves creating three new models:
-
-    // 1. an AggregateWaveModel to provide the mixdowns of the main
-    // model and the new model in its two channels, as input to the
-    // MATCH plugin
-
-    // 2. a SparseTimeValueModel, which is the model automatically
-    // created by FeatureExtractionPluginTransformer when running the
-    // MATCH plugin (thus containing the alignment path)
-
-    // 3. an AlignmentModel, which stores the path model and carries
-    // out alignment lookups on it.
-
-    // The first two of these are provided as arguments to the
-    // constructor for the third, which takes responsibility for
-    // deleting them.  The AlignmentModel, meanwhile, is passed to the
-    // new model we are aligning, which also takes responsibility for
-    // it.  We should not have to delete any of these new models here.
-
-    AggregateWaveModel::ChannelSpecList components;
-
-    components.push_back(AggregateWaveModel::ModelChannelSpec
-                         (m_mainModel, -1));
-
-    components.push_back(AggregateWaveModel::ModelChannelSpec
-                         (rm, -1));
-
-    Model *aggregateModel = new AggregateWaveModel(components);
-    ModelTransformer::Input aggregate(aggregateModel);
-
-    TransformId id = "vamp:match-vamp-plugin:match:path"; //!!! configure
-    
-    TransformFactory *tf = TransformFactory::getInstance();
-
-    Transform transform = tf->getDefaultTransformFor
-        (id, aggregateModel->getSampleRate());
-
-    transform.setStepSize(transform.getBlockSize()/2);
-    transform.setParameter("serialise", 1);
-
-    SVDEBUG << "Document::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
-
-    ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
-
-    QString message;
-    Model *transformOutput = mtf->transform(transform, aggregate, message);
-
-    if (!transformOutput) {
-        transform.setStepSize(0);
-        transformOutput = mtf->transform(transform, aggregate, message);
+    if (!m_align->alignModel(m_mainModel, rm)) {
+        cerr << "Alignment failed: " << m_align->getError() << endl;
+        emit alignmentFailed(m_align->getError());
     }
-
-    SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
-        (transformOutput);
-
-    if (!path) {
-        cerr << "Document::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
-        emit alignmentFailed(id, message);
-        delete transformOutput;
-        delete aggregateModel;
-        return;
-    }
-
-    path->setCompletion(0);
-
-    AlignmentModel *alignmentModel = new AlignmentModel
-        (m_mainModel, model, aggregateModel, path);
-
-    rm->setAlignment(alignmentModel);
 }
 
 void
--- a/framework/Document.h	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/Document.h	Wed Dec 07 11:51:42 2016 +0000
@@ -32,6 +32,8 @@
 
 class AdditionalModelConverter;
 
+class Align;
+
 /**
  * A Sonic Visualiser document consists of a set of data models, and
  * also the visualisation layers used to display them.  Changes to the
@@ -301,7 +303,9 @@
                                  QString message);
     void modelRegenerationWarning(QString layerName, QString transformName,
                                   QString message);
-    void alignmentFailed(QString transformName, QString message);
+
+    void alignmentComplete(AlignmentModel *);
+    void alignmentFailed(QString message);
 
     void activity(QString);
 
@@ -407,8 +411,6 @@
     void writeBackwardCompatibleDerivation(QTextStream &, QString, Model *,
                                            const ModelRecord &) const;
 
-    static TransformId getAlignmentTransformName();
-    
     void toXml(QTextStream &, QString, QString, bool asTemplate) const;
     void writePlaceholderMainModel(QTextStream &, QString) const;
 
@@ -423,6 +425,7 @@
     LayerSet m_layers;
 
     bool m_autoAlignment;
+    Align *m_align;
 };
 
 #endif
--- a/framework/MainWindowBase.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/MainWindowBase.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -16,10 +16,10 @@
 #include "MainWindowBase.h"
 #include "Document.h"
 
-
 #include "view/Pane.h"
 #include "view/PaneStack.h"
-#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
+#include "data/model/WritableWaveFileModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/NoteModel.h"
 #include "data/model/FlexiNoteModel.h"
@@ -48,7 +48,9 @@
 #include "widgets/InteractiveFileFinder.h"
 
 #include "audio/AudioCallbackPlaySource.h"
+#include "audio/AudioRecordTarget.h"
 #include "audio/PlaySpeedRangeMapper.h"
+
 #include "data/fileio/DataFileReaderFactory.h"
 #include "data/fileio/PlaylistFileReader.h"
 #include "data/fileio/WavFileWriter.h"
@@ -58,8 +60,6 @@
 #include "data/fileio/AudioFileReaderFactory.h"
 #include "rdf/RDFImporter.h"
 
-#include "data/fft/FFTDataServer.h"
-
 #include "base/RecentFiles.h"
 
 #include "base/PlayParameterRepository.h"
@@ -74,6 +74,7 @@
 #include "data/midi/MIDIInput.h"
 
 #include <bqaudioio/SystemPlaybackTarget.h>
+#include <bqaudioio/SystemAudioIO.h>
 #include <bqaudioio/AudioFactory.h>
 
 #include <QApplication>
@@ -132,15 +133,16 @@
 #undef Window
 #endif
 
-MainWindowBase::MainWindowBase(bool withAudioOutput,
-                               bool withMIDIInput) :
+MainWindowBase::MainWindowBase(SoundOptions options) :
     m_document(0),
     m_paneStack(0),
     m_viewManager(0),
     m_timeRulerLayer(0),
-    m_audioOutput(withAudioOutput),
+    m_soundOptions(options),
     m_playSource(0),
+    m_recordTarget(0),
     m_playTarget(0),
+    m_audioIO(0),
     m_oscQueue(0),
     m_oscQueueStarter(0),
     m_midiInput(0),
@@ -153,11 +155,19 @@
     m_lastPlayStatusSec(0),
     m_initialDarkBackground(false),
     m_defaultFfwdRwdStep(2, 0),
+    m_audioRecordMode(RecordCreateAdditionalModel),
     m_statusLabel(0),
+    m_iconsVisibleInMenus(true),
     m_menuShortcutMapper(0)
 {
     Profiler profiler("MainWindowBase::MainWindowBase");
 
+    if (options & WithAudioInput) {
+        if (!(options & WithAudioOutput)) {
+            cerr << "WARNING: MainWindowBase: WithAudioInput requires WithAudioOutput -- recording will not work" << endl;
+        }
+    }
+    
     qRegisterMetaType<sv_frame_t>("sv_frame_t");
     qRegisterMetaType<sv_samplerate_t>("sv_samplerate_t");
 
@@ -187,6 +197,7 @@
     settings.setValue("view-font-size", viewFontSize);
     settings.endGroup();
 
+#ifdef NOT_DEFINED // This no longer works correctly on any platform AFAICS
     Preferences::BackgroundMode mode =
         Preferences::getInstance()->getBackgroundMode();
     m_initialDarkBackground = m_viewManager->getGlobalDarkBackground();
@@ -194,6 +205,7 @@
         m_viewManager->setGlobalDarkBackground
             (mode == Preferences::DarkBackground);
     }
+#endif
 
     m_paneStack = new PaneStack(0, m_viewManager);
     connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)),
@@ -219,6 +231,12 @@
 
     m_playSource = new AudioCallbackPlaySource(m_viewManager,
                                                QApplication::applicationName());
+    if (m_soundOptions & WithAudioInput) {
+        m_recordTarget = new AudioRecordTarget(m_viewManager,
+                                               QApplication::applicationName());
+        connect(m_recordTarget, SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)),
+                this, SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t)));
+    }
 
     connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)),
 	    this,           SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)));
@@ -259,7 +277,7 @@
     m_labeller = new Labeller(labellerType);
     m_labeller->setCounterCycleSize(cycle);
 
-    if (withMIDIInput) {
+    if (m_soundOptions & WithMIDIInput) {
         m_midiInput = new MIDIInput(QApplication::applicationName(), this);
     }
 
@@ -269,8 +287,25 @@
 MainWindowBase::~MainWindowBase()
 {
     SVDEBUG << "MainWindowBase::~MainWindowBase" << endl;
+
+    // We have to delete the breakfastquay::SystemPlaybackTarget or
+    // breakfastquay::SystemAudioIO object (whichever we have -- it
+    // depends on whether we handle recording or not) before we delete
+    // the ApplicationPlaybackSource and ApplicationRecordTarget that
+    // they refer to.
+    
+    // First prevent this trying to call target.
+    if (m_playSource) m_playSource->setSystemPlaybackTarget(0);
+
+    // Then delete the breakfastquay::System object.
+    // Only one of these two exists!
+    delete m_audioIO;
     delete m_playTarget;
+
+    // Then delete the Application objects.
     delete m_playSource;
+    delete m_recordTarget;
+    
     delete m_viewManager;
     delete m_oscQueue;
     delete m_oscQueueStarter;
@@ -315,12 +350,12 @@
 }
 
 void
-MainWindowBase::finaliseMenu(QMenu *
-#ifdef Q_OS_MAC
-                             menu
-#endif
-    )
+MainWindowBase::finaliseMenu(QMenu *menu)
 {
+    foreach (QAction *a, menu->actions()) {
+        a->setIconVisibleInMenu(m_iconsVisibleInMenus);
+    }
+
 #ifdef Q_OS_MAC
     // See https://bugreports.qt-project.org/browse/QTBUG-38256 and
     // our issue #890 http://code.soundsoftware.ac.uk/issues/890 --
@@ -552,7 +587,7 @@
     bool haveMainModel =
 	(getMainModel() != 0);
     bool havePlayTarget =
-	(m_playTarget != 0);
+	(m_playTarget != 0 || m_audioIO != 0);
     bool haveSelection = 
 	(m_viewManager &&
 	 !m_viewManager->getSelections().empty());
@@ -597,6 +632,7 @@
     emit canMeasureLayer(haveCurrentLayer);
     emit canSelect(haveMainModel && haveCurrentPane);
     emit canPlay(haveMainModel && havePlayTarget);
+    emit canRecord(m_recordTarget != 0);
     emit canFfwd(haveMainModel);
     emit canRewind(haveMainModel);
     emit canPaste(haveClipboardContents);
@@ -604,6 +640,8 @@
     emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection);
     emit canInsertItemAtSelection(haveCurrentPane && haveSelection && haveCurrentDurationLayer);
     emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection);
+    emit canSubdivideInstants(haveCurrentTimeInstantsLayer && haveSelection);
+    emit canWinnowInstants(haveCurrentTimeInstantsLayer && haveSelection);
     emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection);
     emit canClearSelection(haveSelection);
     emit canEditSelection(haveSelection && haveCurrentEditableLayer);
@@ -1196,9 +1234,60 @@
     Labeller labeller(*m_labeller);
     labeller.setSampleRate(sodm->getSampleRate());
 
-    // This uses a command
-
-    labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms);
+    Command *c = labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms);
+    if (c) CommandHistory::getInstance()->addCommand(c, false);
+}
+
+void
+MainWindowBase::subdivideInstantsBy(int n)
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) return;
+
+    Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
+    if (!layer) return;
+
+    MultiSelection ms(m_viewManager->getSelection());
+    
+    Model *model = layer->getModel();
+    SparseOneDimensionalModel *sodm =
+        dynamic_cast<SparseOneDimensionalModel *>(model);
+    if (!sodm) return;
+
+    if (!m_labeller) return;
+
+    Labeller labeller(*m_labeller);
+    labeller.setSampleRate(sodm->getSampleRate());
+
+    Command *c = labeller.subdivide<SparseOneDimensionalModel::Point>
+        (*sodm, &ms, n);
+    if (c) CommandHistory::getInstance()->addCommand(c, false);
+}
+
+void
+MainWindowBase::winnowInstantsBy(int n)
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) return;
+
+    Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
+    if (!layer) return;
+
+    MultiSelection ms(m_viewManager->getSelection());
+    
+    Model *model = layer->getModel();
+    SparseOneDimensionalModel *sodm =
+        dynamic_cast<SparseOneDimensionalModel *>(model);
+    if (!sodm) return;
+
+    if (!m_labeller) return;
+
+    Labeller labeller(*m_labeller);
+    labeller.setSampleRate(sodm->getSampleRate());
+
+    Command *c = labeller.winnow<SparseOneDimensionalModel::Point>
+        (*sodm, &ms, n);
+    if (c) CommandHistory::getInstance()->addCommand(c, false);
 }
 
 MainWindowBase::FileOpenStatus
@@ -1313,7 +1402,7 @@
         rate = m_playSource->getSourceSampleRate();
     }
 
-    WaveFileModel *newModel = new WaveFileModel(source, rate);
+    ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(source, rate);
 
     if (!newModel->isOK()) {
 	delete newModel;
@@ -1767,6 +1856,51 @@
 }
 
 MainWindowBase::FileOpenStatus
+MainWindowBase::openDirOfAudio(QString dirPath)
+{
+    QDir dir(dirPath);
+    QStringList files = dir.entryList(QDir::Files | QDir::Readable);
+    files.sort();
+
+    FileOpenStatus status = FileOpenFailed;
+    bool first = true;
+    bool cancelled = false;
+
+    foreach (QString file, files) {
+
+        FileSource source(dir.filePath(file));
+        if (!source.isAvailable()) {
+            continue;
+        }
+
+        if (AudioFileReaderFactory::getKnownExtensions().contains
+            (source.getExtension().toLower())) {
+            
+            AudioFileOpenMode mode = CreateAdditionalModel;
+            if (first) mode = ReplaceSession;
+            
+            switch (openAudio(source, mode)) {
+            case FileOpenSucceeded:
+                status = FileOpenSucceeded;
+                first = false;
+                break;
+            case FileOpenFailed:
+                break;
+            case FileOpenCancelled:
+                cancelled = true;
+                break;
+            case FileOpenWrongMode:
+                break;
+            }
+        }
+
+        if (cancelled) break;
+    }
+
+    return status;
+}
+
+MainWindowBase::FileOpenStatus
 MainWindowBase::openSessionPath(QString fileOrUrl)
 {
     ProgressDialog dialog(tr("Opening session..."), true, 2000, this);
@@ -2160,9 +2294,11 @@
 }
 
 void
-MainWindowBase::createPlayTarget()
+MainWindowBase::createAudioIO()
 {
-    if (m_playTarget) return;
+    if (m_playTarget || m_audioIO) return;
+
+    if (!(m_soundOptions & WithAudioOutput)) return;
 
     QSettings settings;
     settings.beginGroup("Preferences");
@@ -2270,8 +2406,10 @@
             this, SLOT(modelGenerationFailed(QString, QString)));
     connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
             this, SLOT(modelRegenerationWarning(QString, QString, QString)));
-    connect(m_document, SIGNAL(alignmentFailed(QString, QString)),
-            this, SLOT(alignmentFailed(QString, QString)));
+    connect(m_document, SIGNAL(alignmentComplete(AlignmentModel *)),
+            this, SLOT(alignmentComplete(AlignmentModel *)));
+    connect(m_document, SIGNAL(alignmentFailed(QString)),
+            this, SLOT(alignmentFailed(QString)));
 
     emit replacedDocument();
 }
@@ -2655,15 +2793,159 @@
 void
 MainWindowBase::play()
 {
-    if (m_playSource->isPlaying()) {
+    if ((m_recordTarget && m_recordTarget->isRecording()) ||
+        (m_playSource && m_playSource->isPlaying())) {
         stop();
+        QAction *action = qobject_cast<QAction *>(sender());
+        if (action) action->setChecked(false);
     } else {
+        if (m_audioIO) m_audioIO->resume();
+        else if (m_playTarget) m_playTarget->resume();
         playbackFrameChanged(m_viewManager->getPlaybackFrame());
 	m_playSource->play(m_viewManager->getPlaybackFrame());
     }
 }
 
 void
+MainWindowBase::record()
+{
+    if (!(m_soundOptions & WithAudioInput)) {
+        return;
+    }
+
+    if (!m_recordTarget) {
+        //!!! report
+        return;
+    }
+
+    if (!m_audioIO) {
+        createAudioIO();
+    }
+
+    if (!m_audioIO) {
+        //!!! report
+        return;
+    }
+    
+    if (m_recordTarget->isRecording()) {
+        stop();
+        return;
+    }
+
+    QAction *action = qobject_cast<QAction *>(sender());
+    
+    if (m_audioRecordMode == RecordReplaceSession) {
+        if (!checkSaveModified()) {
+            if (action) action->setChecked(false);
+            return;
+        }
+    }
+
+    m_audioIO->resume();
+
+    WritableWaveFileModel *model = m_recordTarget->startRecording();
+    if (!model) {
+        cerr << "ERROR: MainWindowBase::record: Recording failed" << endl;
+        //!!! report
+        if (action) action->setChecked(false);
+        return;
+    }
+
+    if (!model->isOK()) {
+        m_recordTarget->stopRecording();
+        m_audioIO->suspend();
+        delete model;
+        return;
+    }
+    
+    PlayParameterRepository::getInstance()->addPlayable(model);
+
+    if (m_audioRecordMode == RecordReplaceSession || !getMainModel()) {
+
+        //!!! duplication with openAudio here
+        
+        QString templateName = getDefaultSessionTemplate();
+        bool loadedTemplate = false;
+        
+        if (templateName != "") {
+            FileOpenStatus tplStatus = openSessionTemplate(templateName);
+            if (tplStatus == FileOpenCancelled) {
+                m_recordTarget->stopRecording();
+                m_audioIO->suspend();
+                PlayParameterRepository::getInstance()->removePlayable(model);
+                return;
+            }
+            if (tplStatus != FileOpenFailed) {
+                loadedTemplate = true;
+            }
+        }
+
+        if (!loadedTemplate) {
+            closeSession();
+            createDocument();
+        }
+        
+        Model *prevMain = getMainModel();
+        if (prevMain) {
+            m_playSource->removeModel(prevMain);
+            PlayParameterRepository::getInstance()->removePlayable(prevMain);
+        }
+        
+        m_document->setMainModel(model);
+        setupMenus();
+
+	if (loadedTemplate || (m_sessionFile == "")) {
+            //!!! shouldn't be dealing directly with title from here -- call a method
+	    setWindowTitle(tr("%1: %2")
+                           .arg(QApplication::applicationName())
+                           .arg(model->getLocation()));
+	    CommandHistory::getInstance()->clear();
+	    CommandHistory::getInstance()->documentSaved();
+	    m_documentModified = false;
+	} else {
+	    setWindowTitle(tr("%1: %2 [%3]")
+                           .arg(QApplication::applicationName())
+			   .arg(QFileInfo(m_sessionFile).fileName())
+			   .arg(model->getLocation()));
+	    if (m_documentModified) {
+		m_documentModified = false;
+		documentModified(); // so as to restore "(modified)" window title
+	    }
+	}
+
+    } else {
+
+        CommandHistory::getInstance()->startCompoundOperation
+            (tr("Import Recorded Audio"), true);
+
+        m_document->addImportedModel(model);
+
+        AddPaneCommand *command = new AddPaneCommand(this);
+        CommandHistory::getInstance()->addCommand(command);
+
+        Pane *pane = command->getPane();
+
+        if (m_timeRulerLayer) {
+            m_document->addLayerToView(pane, m_timeRulerLayer);
+        }
+
+        Layer *newLayer = m_document->createImportedLayer(model);
+
+        if (newLayer) {
+            m_document->addLayerToView(pane, newLayer);
+        }
+	
+        CommandHistory::getInstance()->endCompoundOperation();
+    }
+
+    updateMenuStates();
+    m_recentFiles.addFile(model->getLocation());
+    currentPaneChanged(m_paneStack->getCurrentPane());
+
+    emit audioFileLoaded();
+}
+
+void
 MainWindowBase::ffwd()
 {
     if (!getMainModel()) return;
@@ -2891,8 +3173,18 @@
 void
 MainWindowBase::stop()
 {
+    if (m_recordTarget &&
+        m_recordTarget->isRecording()) {
+        m_recordTarget->stopRecording();
+    }
+
+    if (!m_playSource) return;
+    
     m_playSource->stop();
 
+    if (m_audioIO) m_audioIO->suspend();
+    else if (m_playTarget) m_playTarget->suspend();
+    
     if (m_paneStack && m_paneStack->getCurrentPane()) {
         updateVisibleRangeDisplay(m_paneStack->getCurrentPane());
     } else {
@@ -3231,6 +3523,17 @@
 }
 
 void
+MainWindowBase::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate)
+{
+    RealTime duration = RealTime::frame2RealTime(frame, rate);
+    QString durStr = duration.toSecText().c_str();
+    
+    m_myStatusMessage = tr("Recording: %1").arg(durStr);
+
+    getStatusLabel()->setText(m_myStatusMessage);
+}
+
+void
 MainWindowBase::globalCentreFrameChanged(sv_frame_t )
 {
     if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
@@ -3361,8 +3664,9 @@
 //    SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl;
     updateDescriptionLabel();
     if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
-    if (model && !m_playTarget && m_audioOutput) {
-        createPlayTarget();
+    if (model && !(m_playTarget || m_audioIO) &&
+        (m_soundOptions & WithAudioOutput)) {
+        createAudioIO();
     }
 }
 
@@ -3374,7 +3678,6 @@
         m_viewManager->setPlaybackModel(0);
     }
     m_playSource->removeModel(model);
-    FFTDataServer::modelAboutToBeDeleted(model);
 }
 
 void
@@ -3414,6 +3717,12 @@
 }
 
 void
+MainWindowBase::alignmentComplete(AlignmentModel *model)
+{
+    cerr << "MainWindowBase::alignmentComplete(" << model << ")" << endl;
+}
+
+void
 MainWindowBase::pollOSC()
 {
     if (!m_oscQueue || m_oscQueue->isEmpty()) return;
@@ -3492,4 +3801,30 @@
 #endif
 }
 
-    
+void
+MainWindowBase::openLocalFolder(QString path)
+{
+    QDir d(path);
+    if (d.exists()) {
+        QStringList args;
+        QString path = d.canonicalPath();
+#if defined Q_OS_WIN32
+        // Although the Win32 API is quite happy to have
+        // forward slashes as directory separators, Windows
+        // Explorer is not
+        path = path.replace('/', '\\');
+        args << path;
+        QProcess::execute("c:/windows/explorer.exe", args);
+#else
+        args << path;
+        QProcess::execute(
+#if defined Q_OS_MAC
+            "/usr/bin/open",
+#else
+            "/usr/bin/xdg-open",
+#endif
+            args);
+#endif
+    }
+}
+
--- a/framework/MainWindowBase.h	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/MainWindowBase.h	Wed Dec 07 11:51:42 2016 +0000
@@ -46,6 +46,7 @@
 class WaveformLayer;
 class WaveFileModel;
 class AudioCallbackPlaySource;
+class AudioRecordTarget;
 class CommandHistory;
 class QMenu;
 class AudioDial;
@@ -61,9 +62,11 @@
 class ModelDataTableDialog;
 class QSignalMapper;
 class QShortcut;
+class AlignmentModel;
 
 namespace breakfastquay {
 class SystemPlaybackTarget;
+class SystemAudioIO;
 }
 
 /**
@@ -80,7 +83,16 @@
     Q_OBJECT
 
 public:
-    MainWindowBase(bool withAudioOutput, bool withMIDIInput);
+    enum SoundOption {
+        WithAudioOutput = 0x01,
+        WithAudioInput  = 0x02,
+        WithMIDIInput   = 0x04,
+        WithEverything  = 0xff,
+        WithNothing     = 0x00
+    };
+    typedef int SoundOptions;
+    
+    MainWindowBase(SoundOptions options = WithEverything);
     virtual ~MainWindowBase();
     
     enum AudioFileOpenMode {
@@ -98,6 +110,11 @@
         FileOpenWrongMode // attempted to open layer when no main model present
     };
 
+    enum AudioRecordMode {
+        RecordReplaceSession,
+        RecordCreateAdditionalModel
+    };
+    
     virtual FileOpenStatus open(FileSource source, AudioFileOpenMode = AskUser);
     virtual FileOpenStatus openPath(QString fileOrUrl, AudioFileOpenMode = AskUser);
     virtual FileOpenStatus openAudio(FileSource source, AudioFileOpenMode = AskUser, QString templateName = "");
@@ -105,6 +122,8 @@
     virtual FileOpenStatus openLayer(FileSource source);
     virtual FileOpenStatus openImage(FileSource source);
 
+    virtual FileOpenStatus openDirOfAudio(QString dirPath);
+    
     virtual FileOpenStatus openSession(FileSource source);
     virtual FileOpenStatus openSessionPath(QString fileOrUrl);
     virtual FileOpenStatus openSessionTemplate(QString templateName);
@@ -120,6 +139,10 @@
         m_defaultFfwdRwdStep = step;
     }
 
+    void setAudioRecordMode(AudioRecordMode mode) {
+        m_audioRecordMode = mode;
+    }
+    
 signals:
     // Used to toggle the availability of menu actions
     void canAddPane(bool);
@@ -145,10 +168,13 @@
     void canInsertInstantsAtBoundaries(bool);
     void canInsertItemAtSelection(bool);
     void canRenumberInstants(bool);
+    void canSubdivideInstants(bool);
+    void canWinnowInstants(bool);
     void canDeleteCurrentLayer(bool);
     void canZoom(bool);
     void canScroll(bool);
     void canPlay(bool);
+    void canRecord(bool);
     void canFfwd(bool);
     void canRewind(bool);
     void canPlaySelection(bool);
@@ -199,6 +225,7 @@
     virtual void ffwdEnd();
     virtual void rewind();
     virtual void rewindStart();
+    virtual void record();
     virtual void stop();
 
     virtual void ffwdSimilar();
@@ -226,6 +253,7 @@
     virtual void viewCentreFrameChanged(View *, sv_frame_t);
     virtual void viewZoomLevelChanged(View *, int, bool);
     virtual void outputLevelsChanged(float, float) = 0;
+    virtual void recordDurationChanged(sv_frame_t, sv_samplerate_t);
 
     virtual void currentPaneChanged(Pane *);
     virtual void currentLayerChanged(Pane *, Layer *);
@@ -249,6 +277,8 @@
     virtual void insertItemAtSelection();
     virtual void insertItemAt(sv_frame_t, sv_frame_t);
     virtual void renumberInstants();
+    virtual void subdivideInstantsBy(int);
+    virtual void winnowInstantsBy(int);
 
     virtual void documentModified();
     virtual void documentRestored();
@@ -269,7 +299,9 @@
     virtual void modelGenerationWarning(QString, QString) = 0;
     virtual void modelRegenerationFailed(QString, QString, QString) = 0;
     virtual void modelRegenerationWarning(QString, QString, QString) = 0;
-    virtual void alignmentFailed(QString, QString) = 0;
+
+    virtual void alignmentComplete(AlignmentModel *);
+    virtual void alignmentFailed(QString) = 0;
 
     virtual void rightButtonMenuRequested(Pane *, QPoint point) = 0;
 
@@ -307,9 +339,12 @@
     ViewManager             *m_viewManager;
     Layer                   *m_timeRulerLayer;
 
-    bool                     m_audioOutput;
+    SoundOptions             m_soundOptions;
+
     AudioCallbackPlaySource *m_playSource;
-    breakfastquay::SystemPlaybackTarget *m_playTarget;
+    AudioRecordTarget       *m_recordTarget;
+    breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this...
+    breakfastquay::SystemAudioIO *m_audioIO;           // ... and this exists
 
     class OSCQueueStarter : public QThread
     {
@@ -345,6 +380,8 @@
 
     RealTime                 m_defaultFfwdRwdStep;
 
+    AudioRecordMode          m_audioRecordMode;
+
     mutable QLabel *m_statusLabel;
     QLabel *getStatusLabel() const;
 
@@ -424,18 +461,23 @@
     virtual QString getDefaultSessionTemplate() const;
     virtual void setDefaultSessionTemplate(QString);
 
-    virtual void createPlayTarget();
+    virtual void createAudioIO();
     virtual void openHelpUrl(QString url);
+    virtual void openLocalFolder(QString path);
 
     virtual void setupMenus() = 0;
     virtual void updateVisibleRangeDisplay(Pane *p) const = 0;
     virtual void updatePositionStatusDisplays() const = 0;
 
     // Call this after setting up the menu bar, to fix up single-key
-    // shortcuts on OS/X
+    // shortcuts on OS/X and do any other platform-specific tidying
     virtual void finaliseMenus();
     virtual void finaliseMenu(QMenu *);
 
+    // Call before finaliseMenus if you wish to have a say in this question
+    void setIconsVisibleInMenus(bool visible) { m_iconsVisibleInMenus = visible; }
+    bool m_iconsVisibleInMenus;
+    
     // Only used on OS/X to work around a Qt/Cocoa bug, see finaliseMenus
     QSignalMapper *m_menuShortcutMapper;
     QList<QShortcut *> m_appShortcuts;
--- a/framework/SVFileReader.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/SVFileReader.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -26,7 +26,7 @@
 
 #include "data/fileio/FileFinder.h"
 
-#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
 #include "data/model/EditableDenseThreeDimensionalModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/SparseTimeValueModel.h"
@@ -489,7 +489,7 @@
                 if (mm) rate = mm->getSampleRate();
             }
 
-            model = new WaveFileModel(file, rate);
+            model = new ReadOnlyWaveFileModel(file, rate);
             if (!model->isOK()) {
                 delete model;
                 model = 0;
@@ -1166,7 +1166,7 @@
 
 	for (QStringList::iterator i = data.begin(); i != data.end(); ++i) {
 
-	    if (values.size() == (int)dtdm->getHeight()) {
+	    if (int(values.size()) == dtdm->getHeight()) {
 		if (!warned) {
 		    cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row "
 			      << m_rowNumber << endl;
--- a/framework/TransformUserConfigurator.cpp	Wed Dec 07 11:50:54 2016 +0000
+++ b/framework/TransformUserConfigurator.cpp	Wed Dec 07 11:51:42 2016 +0000
@@ -45,12 +45,12 @@
 {
     if (plugin && plugin->getType() == "Feature Extraction Plugin") {
 	Vamp::Plugin *vp = static_cast<Vamp::Plugin *>(plugin);
-	SVDEBUG << "TransformUserConfigurator::getChannelRange: is a VP" << endl;
+	SVDEBUG << "TransformUserConfigurator::getChannelRange: is a Vamp plugin" << endl;
         minChannels = int(vp->getMinChannelCount());
         maxChannels = int(vp->getMaxChannelCount());
         return true;
     } else {
-	SVDEBUG << "TransformUserConfigurator::getChannelRange: is not a VP" << endl;
+	SVDEBUG << "TransformUserConfigurator::getChannelRange: is not a Vamp plugin" << endl;
         return TransformFactory::getInstance()->
             getTransformChannelRange(identifier, minChannels, maxChannels);
     }
@@ -80,28 +80,9 @@
 
     if (!plugin) return false;
 
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        Vamp::Plugin *vp = static_cast<Vamp::Plugin *>(plugin);
-
-	frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
-
-	std::vector<Vamp::Plugin::OutputDescriptor> od =
-	    vp->getOutputDescriptors();
-
-	cerr << "configure: looking for output: " << output << endl;
-
-	if (od.size() > 1) {
-	    for (size_t i = 0; i < od.size(); ++i) {
-		if (od[i].identifier == output.toStdString()) {
-		    outputLabel = od[i].name.c_str();
-		    outputDescription = od[i].description.c_str();
-		    break;
-		}
-	    }
-        }
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
+    SVDEBUG << "TransformUserConfigurator::configure: identifier " << id << endl;
+    
+    if (RealTimePluginFactory::instanceFor(id)) {
 
         RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
         const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
@@ -130,8 +111,29 @@
 	    SVDEBUG << "Setting auditioning effect" << endl;
             source->setAuditioningEffect(rtp);
         }
+
+    } else {
+
+        Vamp::Plugin *vp = static_cast<Vamp::Plugin *>(plugin);
+
+	frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
+
+	std::vector<Vamp::Plugin::OutputDescriptor> od =
+	    vp->getOutputDescriptors();
+
+//	cerr << "configure: looking for output: " << output << endl;
+
+	if (od.size() > 1) {
+	    for (size_t i = 0; i < od.size(); ++i) {
+		if (od[i].identifier == output.toStdString()) {
+		    outputLabel = od[i].name.c_str();
+		    outputDescription = od[i].description.c_str();
+		    break;
+		}
+	    }
+        }
     }
-
+    
     int sourceChannels = 1;
     if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
 	sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
@@ -182,12 +184,12 @@
     if (selectedInput != "") {
 	if (modelMap.contains(selectedInput)) {
 	    inputModel = modelMap.value(selectedInput);
-	    cerr << "Found selected input \"" << selectedInput << "\" in model map, result is " << inputModel << endl;
+	    SVDEBUG << "Found selected input \"" << selectedInput << "\" in model map, result is " << inputModel << endl;
 	} else {
-	    cerr << "Failed to find selected input \"" << selectedInput << "\" in model map" << endl;
+	    SVDEBUG << "Failed to find selected input \"" << selectedInput << "\" in model map" << endl;
 	}
     } else {
-	cerr << "Selected input empty: \"" << selectedInput << "\"" << endl;
+	SVDEBUG << "Selected input empty: \"" << selectedInput << "\"" << endl;
     }
         
     // Write parameters back to transform object
--- a/svapp.pro	Wed Dec 07 11:50:54 2016 +0000
+++ b/svapp.pro	Wed Dec 07 11:51:42 2016 +0000
@@ -1,66 +1,24 @@
 
 TEMPLATE = lib
 
+INCLUDEPATH += ../vamp-plugin-sdk
+
 exists(config.pri) {
     include(config.pri)
 }
-!exists(config.pri) {
-
-    CONFIG += release
-    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
-
-    win32-g++ {
-        INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
-        LIBS += -L../sv-dependency-builds/win32-mingw/lib
-    }
-    win32-msvc* {
-        INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
-        LIBS += -L../sv-dependency-builds/win32-msvc/lib
-    }
-    macx* {
-        INCLUDEPATH += ../sv-dependency-builds/osx/include
-        LIBS += -L../sv-dependency-builds/osx/lib
-    }
-
-    win* {
-        DEFINES += HAVE_PORTAUDIO
-    }
-    macx* {
-        DEFINES += HAVE_COREAUDIO HAVE_PORTAUDIO
-    }
-}
 
 CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11
 QT += network xml gui widgets
 
 TARGET = svapp
 
-DEPENDPATH += . ../bqaudioio ../svcore ../svgui
-INCLUDEPATH += . ../bqaudioio ../svcore ../svgui
+DEPENDPATH += . ../bqaudioio ../svcore ../svgui ../piper-cpp
+INCLUDEPATH += . ../bqaudioio ../svcore ../svgui ../piper-cpp
 OBJECTS_DIR = o
 MOC_DIR = o
 
-HEADERS += audio/AudioCallbackPlaySource.h \
-           audio/AudioGenerator.h \
-           audio/ClipMixer.h \
-           audio/ContinuousSynth.h \
-           audio/PlaySpeedRangeMapper.h
+include(files.pri)
 
-SOURCES += audio/AudioCallbackPlaySource.cpp \
-           audio/AudioGenerator.cpp \
-           audio/ClipMixer.cpp \
-           audio/ContinuousSynth.cpp \
-           audio/PlaySpeedRangeMapper.cpp
+HEADERS = $$(SVAPP_HEADERS)
+SOURCES = $$(SVAPP_SOURCES)
 
-HEADERS += framework/Document.h \
-           framework/MainWindowBase.h \
-           framework/SVFileReader.h \
-           framework/TransformUserConfigurator.h \
-           framework/VersionTester.h
-
-SOURCES += framework/Document.cpp \
-           framework/MainWindowBase.cpp \
-           framework/SVFileReader.cpp \
-           framework/TransformUserConfigurator.cpp \
-           framework/VersionTester.cpp
-