changeset 927:5f021c13a4cc tony_integration

Merge from branch "tonioni"
author Chris Cannam
date Tue, 17 Jun 2014 12:54:51 +0100
parents 926f33d3a8b7 (current diff) 3efc20c59a94 (diff)
children d03b3d956358
files
diffstat 22 files changed, 210 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/base/Preferences.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/base/Preferences.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -43,7 +43,9 @@
     m_resampleQuality(1),
     m_omitRecentTemps(true),
     m_tempDirRoot(""),
+    m_fixedSampleRate(0),
     m_resampleOnLoad(false),
+    m_normaliseAudio(false),
     m_viewFontSize(10),
     m_backgroundMode(BackgroundFromTheme),
     m_timeToTextMode(TimeToTextMs),
@@ -62,7 +64,9 @@
     m_windowType = WindowType
         (settings.value("window-type", int(HanningWindow)).toInt());
     m_resampleQuality = settings.value("resample-quality", 1).toInt();
+    m_fixedSampleRate = settings.value("fixed-sample-rate", 0).toInt();
     m_resampleOnLoad = settings.value("resample-on-load", false).toBool();
+    m_normaliseAudio = settings.value("normalise-audio", false).toBool();
     m_backgroundMode = BackgroundMode
         (settings.value("background-mode", int(BackgroundFromTheme)).toInt());
     m_timeToTextMode = TimeToTextMode
@@ -93,6 +97,8 @@
     props.push_back("Resample Quality");
     props.push_back("Omit Temporaries from Recent Files");
     props.push_back("Resample On Load");
+    props.push_back("Normalise Audio");
+    props.push_back("Fixed Sample Rate");
     props.push_back("Temporary Directory Root");
     props.push_back("Background Mode");
     props.push_back("Time To Text Mode");
@@ -123,12 +129,18 @@
     if (name == "Resample Quality") {
         return tr("Playback resampler type");
     }
+    if (name == "Normalise Audio") {
+        return tr("Normalise audio signal when reading from audio file");
+    }
     if (name == "Omit Temporaries from Recent Files") {
         return tr("Omit temporaries from Recent Files menu");
     }
     if (name == "Resample On Load") {
         return tr("Resample mismatching files on import");
     }
+    if (name == "Fixed Sample Rate") {
+        return tr("Single fixed sample rate to resample all files to");
+    }
     if (name == "Temporary Directory Root") {
         return tr("Location for cache file directory");
     }
@@ -171,12 +183,18 @@
     if (name == "Resample Quality") {
         return ValueProperty;
     }
+    if (name == "Normalise Audio") {
+        return ToggleProperty;
+    }
     if (name == "Omit Temporaries from Recent Files") {
         return ToggleProperty;
     }
     if (name == "Resample On Load") {
         return ToggleProperty;
     }
+    if (name == "Fixed Sample Rate") {
+        return ValueProperty;
+    }
     if (name == "Temporary Directory Root") {
         // It's an arbitrary string, we don't have a set of values for this
         return InvalidProperty;
@@ -525,6 +543,32 @@
 }
 
 void
+Preferences::setFixedSampleRate(int rate)
+{
+    if (m_fixedSampleRate != rate) {
+        m_fixedSampleRate = rate;
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("fixed-sample-rate", rate);
+        settings.endGroup();
+        emit propertyChanged("Fixed Sample Rate");
+    }
+}
+
+void
+Preferences::setNormaliseAudio(bool norm)
+{
+    if (m_normaliseAudio != norm) {
+        m_normaliseAudio = norm;
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("normalise-audio", norm);
+        settings.endGroup();
+        emit propertyChanged("Normalise Audio");
+    }
+}
+
+void
 Preferences::setBackgroundMode(BackgroundMode mode)
 {
     if (m_backgroundMode != mode) {
--- a/base/Preferences.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/base/Preferences.h	Tue Jun 17 12:54:51 2014 +0100
@@ -66,8 +66,15 @@
 
     QString getTemporaryDirectoryRoot() const { return m_tempDirRoot; }
 
+    /// If we should always resample audio to the same rate, return it; otherwise (the normal case) return 0
+    int getFixedSampleRate() const { return m_fixedSampleRate; }
+
+    /// True if we should resample second or subsequent audio file to match first audio file's rate
     bool getResampleOnLoad() const { return m_resampleOnLoad; }    
     
+    /// True if audio files should be loaded with normalisation (max == 1)
+    bool getNormaliseAudio() const { return m_normaliseAudio; }
+
     enum BackgroundMode {
         BackgroundFromTheme,
         DarkBackground,
@@ -107,7 +114,9 @@
     void setResampleQuality(int quality);
     void setOmitTempsFromRecentFiles(bool omit);
     void setTemporaryDirectoryRoot(QString tempDirRoot);
+    void setFixedSampleRate(int);
     void setResampleOnLoad(bool);
+    void setNormaliseAudio(bool);
     void setBackgroundMode(BackgroundMode mode);
     void setTimeToTextMode(TimeToTextMode mode);
     void setOctaveOfMiddleC(int oct);
@@ -141,7 +150,9 @@
     int m_resampleQuality;
     bool m_omitRecentTemps;
     QString m_tempDirRoot;
+    int m_fixedSampleRate;
     bool m_resampleOnLoad;
+    bool m_normaliseAudio;
     int m_viewFontSize;
     BackgroundMode m_backgroundMode;
     TimeToTextMode m_timeToTextMode;
--- a/base/Selection.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/base/Selection.h	Tue Jun 17 12:54:51 2014 +0100
@@ -21,6 +21,21 @@
 
 #include "XmlExportable.h"
 
+/**
+ * A selection object simply represents a range in time, via start and
+ * end frame.
+ *
+ * The end frame is the index of the frame just *after* the end of the
+ * selection. For example a selection of length 10 frames starting at
+ * time 0 will have start frame 0 and end frame 10. This will be
+ * contiguous with (rather than overlapping with) a selection that
+ * starts at frame 10.
+ *
+ * Any selection with equal start and end frames is empty,
+ * representing "no selection". All empty selections are equal under
+ * the comparison operators. The default constructor makes an empty
+ * selection with start and end frames equal to zero.
+ */
 class Selection
 {
 public:
--- a/data/fileio/AudioFileReaderFactory.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/AudioFileReaderFactory.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -58,21 +58,28 @@
 }
 
 AudioFileReader *
-AudioFileReaderFactory::createReader(FileSource source, size_t targetRate,
+AudioFileReaderFactory::createReader(FileSource source, 
+                                     size_t targetRate,
+                                     bool normalised,
                                      ProgressReporter *reporter)
 {
-    return create(source, targetRate, false, reporter);
+    return create(source, targetRate, normalised, false, reporter);
 }
 
 AudioFileReader *
-AudioFileReaderFactory::createThreadingReader(FileSource source, size_t targetRate,
+AudioFileReaderFactory::createThreadingReader(FileSource source, 
+                                              size_t targetRate,
+                                              bool normalised,
                                               ProgressReporter *reporter)
 {
-    return create(source, targetRate, true, reporter);
+    return create(source, targetRate, normalised, true, reporter);
 }
 
 AudioFileReader *
-AudioFileReaderFactory::create(FileSource source, size_t targetRate, bool threading,
+AudioFileReaderFactory::create(FileSource source, 
+                               size_t targetRate, 
+                               bool normalised,
+                               bool threading,
                                ProgressReporter *reporter)
 {
     QString err;
@@ -102,9 +109,10 @@
 
         if (reader->isOK() &&
             (!reader->isQuicklySeekable() ||
+             normalised ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
 
             delete reader;
             reader = new DecodingWavFileReader
@@ -114,6 +122,7 @@
                  DecodingWavFileReader::ResampleAtOnce,
                  DecodingWavFileReader::CacheInTemporaryFile,
                  targetRate ? targetRate : fileRate,
+                 normalised,
                  reporter);
             if (!reader->isOK()) {
                 delete reader;
@@ -133,6 +142,7 @@
                  OggVorbisFileReader::DecodeAtOnce,
                  OggVorbisFileReader::CacheInTemporaryFile,
                  targetRate,
+                 normalised,
                  reporter);
             if (!reader->isOK()) {
                 delete reader;
@@ -153,6 +163,7 @@
                  MP3FileReader::DecodeAtOnce,
                  MP3FileReader::CacheInTemporaryFile,
                  targetRate,
+                 normalised,
                  reporter);
             if (!reader->isOK()) {
                 delete reader;
@@ -172,6 +183,7 @@
                  QuickTimeFileReader::DecodeAtOnce,
                  QuickTimeFileReader::CacheInTemporaryFile,
                  targetRate,
+                 normalised,
                  reporter);
             if (!reader->isOK()) {
                 delete reader;
@@ -191,6 +203,7 @@
                  CoreAudioFileReader::DecodeAtOnce,
                  CoreAudioFileReader::CacheInTemporaryFile,
                  targetRate,
+                 normalised,
                  reporter);
             if (!reader->isOK()) {
                 delete reader;
@@ -215,9 +228,10 @@
 
         if (reader->isOK() &&
             (!reader->isQuicklySeekable() ||
+             normalised ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
 
             delete reader;
             reader = new DecodingWavFileReader
@@ -227,6 +241,7 @@
                  DecodingWavFileReader::ResampleAtOnce,
                  DecodingWavFileReader::CacheInTemporaryFile,
                  targetRate ? targetRate : fileRate,
+                 normalised,
                  reporter);
         }
 
--- a/data/fileio/AudioFileReaderFactory.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/AudioFileReaderFactory.h	Tue Jun 17 12:54:51 2014 +0100
@@ -43,6 +43,9 @@
      * if you want to find out whether the file is being resampled
      * or not.
      *
+     * If normalised is true, the file data will be normalised to
+     * abs(max) == 1.0. Otherwise the file will not be normalised.
+     *
      * If a ProgressReporter is provided, it will be updated with
      * progress status.  Caller retains ownership of the reporter
      * object.
@@ -51,6 +54,7 @@
      */
     static AudioFileReader *createReader(FileSource source,
                                          size_t targetRate = 0,
+                                         bool normalised = false,
                                          ProgressReporter *reporter = 0);
 
     /**
@@ -65,6 +69,9 @@
      * if you want to find out whether the file is being resampled
      * or not.
      *
+     * If normalised is true, the file data will be normalised to
+     * abs(max) == 1.0. Otherwise the file will not be normalised.
+     *
      * If a ProgressReporter is provided, it will be updated with
      * progress status.  This will only be meaningful if threading
      * mode is not used because the file reader in use does not
@@ -76,11 +83,13 @@
      */
     static AudioFileReader *createThreadingReader(FileSource source,
                                                   size_t targetRate = 0,
+                                                  bool normalised = false,
                                                   ProgressReporter *reporter = 0);
 
 protected:
     static AudioFileReader *create(FileSource source,
                                    size_t targetRate,
+                                   bool normalised,
                                    bool threading,
                                    ProgressReporter *reporter);
 };
--- a/data/fileio/CodedAudioFileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/CodedAudioFileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -28,7 +28,8 @@
 #include <QMutexLocker>
 
 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
-                                           size_t targetRate) :
+                                           size_t targetRate,
+                                           bool normalised) :
     m_cacheMode(cacheMode),
     m_initialised(false),
     m_serialiser(0),
@@ -40,9 +41,12 @@
     m_cacheWriteBufferSize(16384),
     m_resampler(0),
     m_resampleBuffer(0),
-    m_fileFrameCount(0)
+    m_fileFrameCount(0),
+    m_normalised(normalised),
+    m_max(0.f),
+    m_gain(1.f)
 {
-    SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << endl;
+    SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
 
     m_frameCount = 0;
     m_sampleRate = targetRate;
@@ -270,11 +274,9 @@
         return;
     }
 
-//    if (m_cacheWriteBufferIndex > 0) {
-        pushBuffer(m_cacheWriteBuffer,
-                   m_cacheWriteBufferIndex / m_channelCount,
-                   true);
-//    }        
+    pushBuffer(m_cacheWriteBuffer,
+               m_cacheWriteBufferIndex / m_channelCount,
+               true);
 
     delete[] m_cacheWriteBuffer;
     m_cacheWriteBuffer = 0;
@@ -312,14 +314,24 @@
 void
 CodedAudioFileReader::pushBufferNonResampling(float *buffer, size_t sz)
 {
-    float max = 1.0;
+    float clip = 1.0;
     size_t count = sz * m_channelCount;
 
-    for (size_t i = 0; i < count; ++i) {
-        if (buffer[i] >  max) buffer[i] =  max;
-    }
-    for (size_t i = 0; i < count; ++i) {
-        if (buffer[i] < -max) buffer[i] = -max;
+    if (m_normalised) {
+        for (size_t i = 0; i < count; ++i) {
+            float v = fabsf(buffer[i]);
+            if (v > m_max) {
+                m_max = v;
+                m_gain = 1.f / m_max;
+            }
+        }
+    } else {
+        for (size_t i = 0; i < count; ++i) {
+            if (buffer[i] >  clip) buffer[i] =  clip;
+        }
+        for (size_t i = 0; i < count; ++i) {
+            if (buffer[i] < -clip) buffer[i] = -clip;
+        }
     }
 
     m_frameCount += sz;
@@ -432,5 +444,11 @@
         m_dataLock.unlock();
     }
     }
+
+    if (m_normalised) {
+        for (int i = 0; i < (int)(count * m_channelCount); ++i) {
+            frames[i] *= m_gain;
+        }
+    }
 }
 
--- a/data/fileio/CodedAudioFileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/CodedAudioFileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -50,7 +50,9 @@
     void progress(int);
 
 protected:
-    CodedAudioFileReader(CacheMode cacheMode, size_t targetRate);
+    CodedAudioFileReader(CacheMode cacheMode,
+                         size_t targetRate,
+                         bool normalised);
 
     void initialiseDecodeCache(); // samplerate, channels must have been set
 
@@ -91,6 +93,10 @@
     Resampler *m_resampler;
     float *m_resampleBuffer;
     size_t m_fileFrameCount;
+
+    bool m_normalised;
+    float m_max;
+    float m_gain;
 };
 
 #endif
--- a/data/fileio/CoreAudioFileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/CoreAudioFileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -60,8 +60,9 @@
                                          DecodeMode decodeMode,
                                          CacheMode mode,
                                          size_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_d(new D),
--- a/data/fileio/CoreAudioFileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/CoreAudioFileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -40,6 +40,7 @@
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
                         size_t targetRate = 0,
+                        bool normalised = false,
                         ProgressReporter *reporter = 0);
     virtual ~CoreAudioFileReader();
 
--- a/data/fileio/DecodingWavFileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/DecodingWavFileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -22,11 +22,12 @@
 #include <QFileInfo>
 
 DecodingWavFileReader::DecodingWavFileReader(FileSource source,
-						 ResampleMode resampleMode,
-						 CacheMode mode,
-						 size_t targetRate,
-                                                 ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+                                             ResampleMode resampleMode,
+                                             CacheMode mode,
+                                             size_t targetRate,
+                                             bool normalised,
+                                             ProgressReporter *reporter) :
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_cancelled(false),
--- a/data/fileio/DecodingWavFileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/DecodingWavFileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -35,10 +35,11 @@
     };
 
     DecodingWavFileReader(FileSource source,
-                            ResampleMode resampleMode,
-                            CacheMode cacheMode,
-                            size_t targetRate = 0,
-                            ProgressReporter *reporter = 0);
+                          ResampleMode resampleMode,
+                          CacheMode cacheMode,
+                          size_t targetRate = 0,
+                          bool normalised = false,
+                          ProgressReporter *reporter = 0);
     virtual ~DecodingWavFileReader();
 
     virtual QString getError() const { return m_error; }
--- a/data/fileio/MP3FileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/MP3FileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -38,8 +38,9 @@
 
 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, 
                              CacheMode mode, size_t targetRate,
+                             bool normalised,
                              ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_decodeThread(0)
--- a/data/fileio/MP3FileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/MP3FileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -41,6 +41,7 @@
                   DecodeMode decodeMode,
                   CacheMode cacheMode,
                   size_t targetRate = 0,
+                  bool normalised = false,
                   ProgressReporter *reporter = 0);
     virtual ~MP3FileReader();
 
--- a/data/fileio/OggVorbisFileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/OggVorbisFileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -35,8 +35,9 @@
                                          DecodeMode decodeMode,
                                          CacheMode mode,
                                          size_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_reporter(reporter),
--- a/data/fileio/OggVorbisFileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/OggVorbisFileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -43,6 +43,7 @@
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
                         size_t targetRate = 0,
+                        bool normalised = false,
                         ProgressReporter *reporter = 0);
     virtual ~OggVorbisFileReader();
 
--- a/data/fileio/QuickTimeFileReader.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/QuickTimeFileReader.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -51,8 +51,9 @@
                                          DecodeMode decodeMode,
                                          CacheMode mode,
                                          size_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_d(new D),
--- a/data/fileio/QuickTimeFileReader.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/fileio/QuickTimeFileReader.h	Tue Jun 17 12:54:51 2014 +0100
@@ -43,6 +43,7 @@
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
                         size_t targetRate = 0,
+                        bool normalised = false,
                         ProgressReporter *reporter = 0);
     virtual ~QuickTimeFileReader();
 
--- a/data/model/Model.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/model/Model.h	Tue Jun 17 12:54:51 2014 +0100
@@ -105,7 +105,27 @@
      * Caller owns the returned value.
      */
     virtual Model *clone() const = 0;
-    
+
+    /**
+     * Mark the model as abandoning. This means that the application
+     * no longer needs it, so it can stop doing any background
+     * calculations it may be involved in. Note that as far as the
+     * model API is concerned, this does nothing more than tell the
+     * model to return true from isAbandoning().  The actual response
+     * to this will depend on the model's context -- it's possible
+     * nothing at all will change.
+     */
+    virtual void abandon() {
+        m_abandoning = true;
+    }
+
+    /**
+     * Query whether the model has been marked as abandoning.
+     */
+    virtual bool isAbandoning() const { 
+        return m_abandoning;
+    }
+
     /**
      * Return true if the model has finished loading or calculating
      * all its data, for a model that is capable of calculating in a
@@ -268,7 +288,11 @@
     void aboutToBeDeleted();
 
 protected:
-    Model() : m_sourceModel(0), m_alignment(0), m_aboutToDelete(false) { }
+    Model() : 
+        m_sourceModel(0), 
+        m_alignment(0), 
+        m_abandoning(false), 
+        m_aboutToDelete(false) { }
 
     // Not provided.
     Model(const Model &);
@@ -277,6 +301,7 @@
     Model *m_sourceModel;
     AlignmentModel *m_alignment;
     QString m_typeUri;
+    bool m_abandoning;
     bool m_aboutToDelete;
 };
 
--- a/data/model/WaveFileModel.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/data/model/WaveFileModel.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -20,6 +20,8 @@
 
 #include "system/System.h"
 
+#include "base/Preferences.h"
+
 #include <QFileInfo>
 #include <QTextStream>
 
@@ -49,8 +51,9 @@
 {
     m_source.waitForData();
     if (m_source.isOK()) {
+        bool normalise = Preferences::getInstance()->getNormaliseAudio();
         m_reader = AudioFileReaderFactory::createThreadingReader
-            (m_source, targetRate);
+            (m_source, targetRate, normalise);
         if (m_reader) {
             SVDEBUG << "WaveFileModel::WaveFileModel: reader rate: "
                       << m_reader->getSampleRate() << endl;
--- a/transform/FeatureExtractionModelTransformer.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -1018,6 +1018,7 @@
 	SparseOneDimensionalModel *model =
             getConformingOutput<SparseOneDimensionalModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
     } else if (isOutput<SparseTimeValueModel>(n)) {
@@ -1025,24 +1026,28 @@
 	SparseTimeValueModel *model =
             getConformingOutput<SparseTimeValueModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
     } else if (isOutput<NoteModel>(n)) {
 
 	NoteModel *model = getConformingOutput<NoteModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 	
-	} else if (isOutput<FlexiNoteModel>(n)) {
+    } else if (isOutput<FlexiNoteModel>(n)) {
 
 	FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
     } else if (isOutput<RegionModel>(n)) {
 
 	RegionModel *model = getConformingOutput<RegionModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
     } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
@@ -1050,6 +1055,7 @@
 	EditableDenseThreeDimensionalModel *model =
             getConformingOutput<EditableDenseThreeDimensionalModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true); //!!!m_context.updates);
     }
 }
--- a/transform/ModelTransformerFactory.cpp	Tue Jun 17 12:52:27 2014 +0100
+++ b/transform/ModelTransformerFactory.cpp	Tue Jun 17 12:54:51 2014 +0100
@@ -106,7 +106,6 @@
     } else if (RealTimePluginFactory::instanceFor(id)) {
 
         RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
-        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
 
         size_t sampleRate = inputModel->getSampleRate();
         size_t blockSize = 1024;
@@ -234,7 +233,7 @@
         QString trn =
             TransformFactory::getInstance()->getTransformFriendlyName
             (transforms[0].getIdentifier());
-        for (int i = 0; i < models.size(); ++i) {
+        for (int i = 0; i < (int)models.size(); ++i) {
             if (imn != "") {
                 if (trn != "") {
                     models[i]->setObjectName(tr("%1: %2").arg(imn).arg(trn));
--- a/transform/ModelTransformerFactory.h	Tue Jun 17 12:52:27 2014 +0100
+++ b/transform/ModelTransformerFactory.h	Tue Jun 17 12:54:51 2014 +0100
@@ -84,7 +84,8 @@
      * Return the output model resulting from applying the named
      * transform to the given input model.  The transform may still be
      * working in the background when the model is returned; check the
-     * output model's isReady completion status for more details.
+     * output model's isReady completion status for more details. To
+     * cancel a background transform, call abandon() on its model.
      *
      * If the transform is unknown or the input model is not an
      * appropriate type for the given transform, or if some other
@@ -116,7 +117,8 @@
      * (as appropriate). Models will be returned in the same order as
      * the transforms were given. The plugin may still be working in
      * the background when the model is returned; check the output
-     * models' isReady completion statuses for more details.
+     * models' isReady completion statuses for more details. To cancel
+     * a background transform, call abandon() on its model.
      *
      * If a transform is unknown or the transforms are insufficiently
      * closely related or the input model is not an appropriate type
@@ -130,7 +132,10 @@
      * is provided here, its moreModelsAvailable method will be called
      * when those models become available, and ownership of those
      * models will be transferred to the handler. Otherwise (if the
-     * handler is null) any such models will be discarded.
+     * handler is null) any such models will be discarded. Note that
+     * calling abandon() on any one of the models returned by
+     * transformMultiple is sufficient to cancel all background
+     * transform activity associated with these output models.
      *
      * The returned models are owned by the caller and must be deleted
      * when no longer needed.