# HG changeset patch # User Chris Cannam # Date 1403076886 -3600 # Node ID d03b3d95635863c6909efcabe63e49f0a97499c4 # Parent e06f03013f4647c512b7b4795d375e67f3168d7d# Parent 5f021c13a4cccf895559c80ce63cd35143913981 Merge from branch tony_integration diff -r e06f03013f46 -r d03b3d956358 base/Preferences.cpp --- a/base/Preferences.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/base/Preferences.cpp Wed Jun 18 08:34:46 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) { diff -r e06f03013f46 -r d03b3d956358 base/Preferences.h --- a/base/Preferences.h Tue Jun 17 16:42:51 2014 +0100 +++ b/base/Preferences.h Wed Jun 18 08:34:46 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; diff -r e06f03013f46 -r d03b3d956358 base/Selection.h --- a/base/Selection.h Tue Jun 17 16:42:51 2014 +0100 +++ b/base/Selection.h Wed Jun 18 08:34:46 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: diff -r e06f03013f46 -r d03b3d956358 data/fileio/AudioFileReaderFactory.cpp --- a/data/fileio/AudioFileReaderFactory.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/AudioFileReaderFactory.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -58,21 +58,28 @@ } AudioFileReader * -AudioFileReaderFactory::createReader(FileSource source, int targetRate, +AudioFileReaderFactory::createReader(FileSource source, + int targetRate, + bool normalised, ProgressReporter *reporter) { - return create(source, targetRate, false, reporter); + return create(source, targetRate, normalised, false, reporter); } AudioFileReader * -AudioFileReaderFactory::createThreadingReader(FileSource source, int targetRate, +AudioFileReaderFactory::createThreadingReader(FileSource source, + int targetRate, + bool normalised, ProgressReporter *reporter) { - return create(source, targetRate, true, reporter); + return create(source, targetRate, normalised, true, reporter); } AudioFileReader * -AudioFileReaderFactory::create(FileSource source, int targetRate, bool threading, +AudioFileReaderFactory::create(FileSource source, + int targetRate, + bool normalised, + bool threading, ProgressReporter *reporter) { QString err; @@ -102,9 +109,10 @@ if (reader->isOK() && (!reader->isQuicklySeekable() || - (targetRate != 0 && fileRate != (int)targetRate))) { + 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() || - (targetRate != 0 && fileRate != (int)targetRate))) { + 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); } diff -r e06f03013f46 -r d03b3d956358 data/fileio/AudioFileReaderFactory.h --- a/data/fileio/AudioFileReaderFactory.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/AudioFileReaderFactory.h Wed Jun 18 08:34:46 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, int 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, int targetRate = 0, + bool normalised = false, ProgressReporter *reporter = 0); protected: static AudioFileReader *create(FileSource source, int targetRate, + bool normalised, bool threading, ProgressReporter *reporter); }; diff -r e06f03013f46 -r d03b3d956358 data/fileio/CodedAudioFileReader.cpp --- a/data/fileio/CodedAudioFileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/CodedAudioFileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -28,7 +28,8 @@ #include CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode, - int targetRate) : + int 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, int sz) { - float max = 1.0; + float clip = 1.0; int count = sz * m_channelCount; - for (int i = 0; i < count; ++i) { - if (buffer[i] > max) buffer[i] = max; - } - for (int i = 0; i < count; ++i) { - if (buffer[i] < -max) buffer[i] = -max; + if (m_normalised) { + for (int 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 (int i = 0; i < count; ++i) { + if (buffer[i] > clip) buffer[i] = clip; + } + for (int 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; + } + } } diff -r e06f03013f46 -r d03b3d956358 data/fileio/CodedAudioFileReader.h --- a/data/fileio/CodedAudioFileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/CodedAudioFileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -50,7 +50,9 @@ void progress(int); protected: - CodedAudioFileReader(CacheMode cacheMode, int targetRate); + CodedAudioFileReader(CacheMode cacheMode, + int targetRate, + bool normalised); void initialiseDecodeCache(); // samplerate, channels must have been set @@ -91,6 +93,10 @@ Resampler *m_resampler; float *m_resampleBuffer; int m_fileFrameCount; + + bool m_normalised; + float m_max; + float m_gain; }; #endif diff -r e06f03013f46 -r d03b3d956358 data/fileio/CoreAudioFileReader.cpp --- a/data/fileio/CoreAudioFileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/CoreAudioFileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -60,8 +60,9 @@ DecodeMode decodeMode, CacheMode mode, int targetRate, + bool normalised, ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate), + CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), m_d(new D), diff -r e06f03013f46 -r d03b3d956358 data/fileio/CoreAudioFileReader.h --- a/data/fileio/CoreAudioFileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/CoreAudioFileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -40,6 +40,7 @@ DecodeMode decodeMode, CacheMode cacheMode, int targetRate = 0, + bool normalised = false, ProgressReporter *reporter = 0); virtual ~CoreAudioFileReader(); diff -r e06f03013f46 -r d03b3d956358 data/fileio/DecodingWavFileReader.cpp --- a/data/fileio/DecodingWavFileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/DecodingWavFileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -22,11 +22,12 @@ #include DecodingWavFileReader::DecodingWavFileReader(FileSource source, - ResampleMode resampleMode, - CacheMode mode, - int targetRate, - ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate), + ResampleMode resampleMode, + CacheMode mode, + int targetRate, + bool normalised, + ProgressReporter *reporter) : + CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), m_cancelled(false), diff -r e06f03013f46 -r d03b3d956358 data/fileio/DecodingWavFileReader.h --- a/data/fileio/DecodingWavFileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/DecodingWavFileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -35,10 +35,11 @@ }; DecodingWavFileReader(FileSource source, - ResampleMode resampleMode, - CacheMode cacheMode, - int targetRate = 0, - ProgressReporter *reporter = 0); + ResampleMode resampleMode, + CacheMode cacheMode, + int targetRate = 0, + bool normalised = false, + ProgressReporter *reporter = 0); virtual ~DecodingWavFileReader(); virtual QString getError() const { return m_error; } diff -r e06f03013f46 -r d03b3d956358 data/fileio/MP3FileReader.cpp --- a/data/fileio/MP3FileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/MP3FileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -38,8 +38,9 @@ MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, CacheMode mode, int targetRate, + bool normalised, ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate), + CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), m_decodeThread(0) diff -r e06f03013f46 -r d03b3d956358 data/fileio/MP3FileReader.h --- a/data/fileio/MP3FileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/MP3FileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -41,6 +41,7 @@ DecodeMode decodeMode, CacheMode cacheMode, int targetRate = 0, + bool normalised = false, ProgressReporter *reporter = 0); virtual ~MP3FileReader(); diff -r e06f03013f46 -r d03b3d956358 data/fileio/OggVorbisFileReader.cpp --- a/data/fileio/OggVorbisFileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/OggVorbisFileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -35,8 +35,9 @@ DecodeMode decodeMode, CacheMode mode, int targetRate, + bool normalised, ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate), + CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), m_reporter(reporter), diff -r e06f03013f46 -r d03b3d956358 data/fileio/OggVorbisFileReader.h --- a/data/fileio/OggVorbisFileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/OggVorbisFileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -43,6 +43,7 @@ DecodeMode decodeMode, CacheMode cacheMode, int targetRate = 0, + bool normalised = false, ProgressReporter *reporter = 0); virtual ~OggVorbisFileReader(); diff -r e06f03013f46 -r d03b3d956358 data/fileio/QuickTimeFileReader.cpp --- a/data/fileio/QuickTimeFileReader.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/QuickTimeFileReader.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -51,8 +51,9 @@ DecodeMode decodeMode, CacheMode mode, int targetRate, + bool normalised, ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate), + CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), m_d(new D), diff -r e06f03013f46 -r d03b3d956358 data/fileio/QuickTimeFileReader.h --- a/data/fileio/QuickTimeFileReader.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/fileio/QuickTimeFileReader.h Wed Jun 18 08:34:46 2014 +0100 @@ -43,6 +43,7 @@ DecodeMode decodeMode, CacheMode cacheMode, int targetRate = 0, + bool normalised = false, ProgressReporter *reporter = 0); virtual ~QuickTimeFileReader(); diff -r e06f03013f46 -r d03b3d956358 data/model/Model.h --- a/data/model/Model.h Tue Jun 17 16:42:51 2014 +0100 +++ b/data/model/Model.h Wed Jun 18 08:34:46 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; }; diff -r e06f03013f46 -r d03b3d956358 data/model/WaveFileModel.cpp --- a/data/model/WaveFileModel.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/data/model/WaveFileModel.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -20,6 +20,8 @@ #include "system/System.h" +#include "base/Preferences.h" + #include #include @@ -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; diff -r e06f03013f46 -r d03b3d956358 transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Tue Jun 17 16:42:51 2014 +0100 +++ b/transform/FeatureExtractionModelTransformer.cpp Wed Jun 18 08:34:46 2014 +0100 @@ -1013,6 +1013,7 @@ SparseOneDimensionalModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); } else if (isOutput(n)) { @@ -1020,24 +1021,28 @@ SparseTimeValueModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); } else if (isOutput(n)) { NoteModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); - } else if (isOutput(n)) { + } else if (isOutput(n)) { FlexiNoteModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); } else if (isOutput(n)) { RegionModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); } else if (isOutput(n)) { @@ -1045,6 +1050,7 @@ EditableDenseThreeDimensionalModel *model = getConformingOutput(n); if (!model) return; + if (model->isAbandoning()) abandon(); model->setCompletion(completion, true); //!!!m_context.updates); } } diff -r e06f03013f46 -r d03b3d956358 transform/ModelTransformerFactory.h --- a/transform/ModelTransformerFactory.h Tue Jun 17 16:42:51 2014 +0100 +++ b/transform/ModelTransformerFactory.h Wed Jun 18 08:34:46 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.