changeset 331:f620ce48c950

* Further naming change: Transformer -> ModelTransformer. The Transform class now describes a thing that can be done, and the ModelTransformer does it to a Model.
author Chris Cannam
date Wed, 07 Nov 2007 12:59:01 +0000
parents 6e9dcf09b7fe
children 13e5870040e6
files plugin/plugin.pro plugin/transform/FeatureExtractionModelTransformer.cpp plugin/transform/FeatureExtractionModelTransformer.h plugin/transform/FeatureExtractionPluginTransformer.cpp plugin/transform/FeatureExtractionPluginTransformer.h plugin/transform/ModelTransformer.cpp plugin/transform/ModelTransformer.h plugin/transform/ModelTransformerFactory.cpp plugin/transform/ModelTransformerFactory.h plugin/transform/PluginTransformer.cpp plugin/transform/PluginTransformer.h plugin/transform/RealTimeEffectModelTransformer.cpp plugin/transform/RealTimeEffectModelTransformer.h plugin/transform/RealTimePluginTransformer.cpp plugin/transform/RealTimePluginTransformer.h plugin/transform/Transformer.cpp plugin/transform/Transformer.h plugin/transform/TransformerFactory.cpp plugin/transform/TransformerFactory.h
diffstat 19 files changed, 2064 insertions(+), 2070 deletions(-) [+]
line wrap: on
line diff
--- a/plugin/plugin.pro	Tue Nov 06 17:15:00 2007 +0000
+++ b/plugin/plugin.pro	Wed Nov 07 12:59:01 2007 +0000
@@ -35,14 +35,14 @@
            api/alsa/seq_event.h \
            api/alsa/seq_midi_event.h \
            api/alsa/sound/asequencer.h \
-           transform/FeatureExtractionPluginTransformer.h \
+           transform/FeatureExtractionModelTransformer.h \
            transform/PluginTransformer.h \
-           transform/RealTimePluginTransformer.h \
+           transform/RealTimeEffectModelTransformer.h \
            transform/Transform.h \
            transform/TransformDescription.h \
            transform/TransformFactory.h \
-           transform/Transformer.h \
-           transform/TransformerFactory.h
+           transform/ModelTransformer.h \
+           transform/ModelTransformerFactory.h
 SOURCES += DSSIPluginFactory.cpp \
            DSSIPluginInstance.cpp \
            FeatureExtractionPluginFactory.cpp \
@@ -54,10 +54,10 @@
            RealTimePluginInstance.cpp \
            api/dssi_alsa_compat.c \
            plugins/SamplePlayer.cpp \
-           transform/FeatureExtractionPluginTransformer.cpp \
+           transform/FeatureExtractionModelTransformer.cpp \
            transform/PluginTransformer.cpp \
-           transform/RealTimePluginTransformer.cpp \
+           transform/RealTimeEffectModelTransformer.cpp \
            transform/Transform.cpp \
            transform/TransformFactory.cpp \
-           transform/Transformer.cpp \
-           transform/TransformerFactory.cpp
+           transform/ModelTransformer.cpp \
+           transform/ModelTransformerFactory.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionModelTransformer.cpp	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,553 @@
+/* -*- 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 "FeatureExtractionModelTransformer.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/PluginXml.h"
+#include "vamp-sdk/Plugin.h"
+
+#include "data/model/Model.h"
+#include "base/Window.h"
+#include "data/model/SparseOneDimensionalModel.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/EditableDenseThreeDimensionalModel.h"
+#include "data/model/DenseTimeValueModel.h"
+#include "data/model/NoteModel.h"
+#include "data/model/FFTModel.h"
+#include "data/model/WaveFileModel.h"
+
+#include <QMessageBox>
+
+#include <iostream>
+
+FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Model *inputModel,
+								   QString pluginId,
+                                                                   const ExecutionContext &context,
+                                                                   QString configurationXml,
+								   QString outputName) :
+    PluginTransformer(inputModel, context),
+    m_plugin(0),
+    m_descriptor(0),
+    m_outputFeatureNo(0)
+{
+//    std::cerr << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl;
+
+    FeatureExtractionPluginFactory *factory =
+	FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+	std::cerr << "FeatureExtractionModelTransformer: No factory available for plugin id \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate());
+
+    if (!m_plugin) {
+	std::cerr << "FeatureExtractionModelTransformer: Failed to instantiate plugin \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    if (configurationXml != "") {
+        PluginXml(m_plugin).setParametersFromXml(configurationXml);
+    }
+
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    size_t channelCount = input->getChannelCount();
+    if (m_plugin->getMaxChannelCount() < channelCount) {
+	channelCount = 1;
+    }
+    if (m_plugin->getMinChannelCount() > channelCount) {
+	std::cerr << "FeatureExtractionModelTransformer:: "
+		  << "Can't provide enough channels to plugin (plugin min "
+		  << m_plugin->getMinChannelCount() << ", max "
+		  << m_plugin->getMaxChannelCount() << ", input model has "
+		  << input->getChannelCount() << ")" << std::endl;
+	return;
+    }
+
+    std::cerr << "Initialising feature extraction plugin with channels = "
+              << channelCount << ", step = " << m_context.stepSize
+              << ", block = " << m_context.blockSize << std::endl;
+
+    if (!m_plugin->initialise(channelCount,
+                              m_context.stepSize,
+                              m_context.blockSize)) {
+        std::cerr << "FeatureExtractionModelTransformer: Plugin "
+                  << m_plugin->getIdentifier() << " failed to initialise!" << std::endl;
+        return;
+    }
+
+    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
+
+    if (outputs.empty()) {
+	std::cerr << "FeatureExtractionModelTransformer: Plugin \""
+		  << pluginId.toStdString() << "\" has no outputs" << std::endl;
+	return;
+    }
+    
+    for (size_t i = 0; i < outputs.size(); ++i) {
+	if (outputName == "" || outputs[i].identifier == outputName.toStdString()) {
+	    m_outputFeatureNo = i;
+	    m_descriptor = new Vamp::Plugin::OutputDescriptor
+		(outputs[i]);
+	    break;
+	}
+    }
+
+    if (!m_descriptor) {
+	std::cerr << "FeatureExtractionModelTransformer: Plugin \""
+		  << pluginId.toStdString() << "\" has no output named \""
+		  << outputName.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer: output sample type "
+//	      << m_descriptor->sampleType << std::endl;
+
+    int binCount = 1;
+    float minValue = 0.0, maxValue = 0.0;
+    bool haveExtents = false;
+    
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer: output bin count "
+//	      << binCount << std::endl;
+
+    if (binCount > 0 && m_descriptor->hasKnownExtents) {
+	minValue = m_descriptor->minValue;
+	maxValue = m_descriptor->maxValue;
+        haveExtents = true;
+    }
+
+    size_t modelRate = m_input->getSampleRate();
+    size_t modelResolution = 1;
+    
+    switch (m_descriptor->sampleType) {
+
+    case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
+	if (m_descriptor->sampleRate != 0.0) {
+	    modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001);
+	}
+	break;
+
+    case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
+	modelResolution = m_context.stepSize;
+	break;
+
+    case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
+	modelRate = size_t(m_descriptor->sampleRate + 0.001);
+	break;
+    }
+
+    if (binCount == 0) {
+
+	m_output = new SparseOneDimensionalModel(modelRate, modelResolution,
+						 false);
+
+    } else if (binCount == 1) {
+
+        SparseTimeValueModel *model;
+        if (haveExtents) {
+            model = new SparseTimeValueModel
+                (modelRate, modelResolution, minValue, maxValue, false);
+        } else {
+            model = new SparseTimeValueModel
+                (modelRate, modelResolution, false);
+        }
+        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
+
+        m_output = model;
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+        // We don't have a sparse 3D model, so interpret this as a
+        // note model.  There's nothing to define which values to use
+        // as which parameters of the note -- for the moment let's
+        // treat the first as pitch, second as duration in frames,
+        // third (if present) as velocity. (Our note model doesn't
+        // yet store velocity.)
+        //!!! todo: ask the user!
+	
+        NoteModel *model;
+        if (haveExtents) {
+            model = new NoteModel
+                (modelRate, modelResolution, minValue, maxValue, false);
+        } else {
+            model = new NoteModel
+                (modelRate, modelResolution, false);
+        }            
+        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
+
+        m_output = model;
+
+    } else {
+
+        EditableDenseThreeDimensionalModel *model =
+            new EditableDenseThreeDimensionalModel
+            (modelRate, modelResolution, binCount, false);
+
+	if (!m_descriptor->binNames.empty()) {
+	    std::vector<QString> names;
+	    for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) {
+		names.push_back(m_descriptor->binNames[i].c_str());
+	    }
+	    model->setBinNames(names);
+	}
+        
+        m_output = model;
+    }
+}
+
+FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
+{
+    std::cerr << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << std::endl;
+    delete m_plugin;
+    delete m_descriptor;
+}
+
+DenseTimeValueModel *
+FeatureExtractionModelTransformer::getInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "FeatureExtractionModelTransformer::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+FeatureExtractionModelTransformer::run()
+{
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    if (!m_output) return;
+
+    while (!input->isReady()) {
+/*
+        if (dynamic_cast<WaveFileModel *>(input)) {
+            std::cerr << "FeatureExtractionModelTransformer::run: Model is not ready, but it's not a WaveFileModel (it's a " << typeid(input).name() << "), so that's OK" << std::endl;
+            sleep(2);
+            break; // no need to wait
+        }
+*/
+        std::cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    size_t sampleRate = m_input->getSampleRate();
+
+    size_t channelCount = input->getChannelCount();
+    if (m_plugin->getMaxChannelCount() < channelCount) {
+	channelCount = 1;
+    }
+
+    float **buffers = new float*[channelCount];
+    for (size_t ch = 0; ch < channelCount; ++ch) {
+	buffers[ch] = new float[m_context.blockSize + 2];
+    }
+
+    bool frequencyDomain = (m_plugin->getInputDomain() ==
+                            Vamp::Plugin::FrequencyDomain);
+    std::vector<FFTModel *> fftModels;
+
+    if (frequencyDomain) {
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            FFTModel *model = new FFTModel
+                                  (getInput(),
+                                   channelCount == 1 ? m_context.channel : ch,
+                                   m_context.windowType,
+                                   m_context.blockSize,
+                                   m_context.stepSize,
+                                   m_context.blockSize,
+                                   false);
+            if (!model->isOK()) {
+                QMessageBox::critical
+                    (0, tr("FFT cache failed"),
+                     tr("Failed to create the FFT model for this transform.\n"
+                        "There may be insufficient memory or disc space to continue."));
+                delete model;
+                setCompletion(100);
+                return;
+            }
+            model->resume();
+            fftModels.push_back(model);
+        }
+    }
+
+    long startFrame = m_input->getStartFrame();
+    long   endFrame = m_input->getEndFrame();
+
+    long contextStart = m_context.startFrame;
+    long contextDuration = m_context.duration;
+
+    if (contextStart == 0 || contextStart < startFrame) {
+        contextStart = startFrame;
+    }
+
+    if (contextDuration == 0) {
+        contextDuration = endFrame - contextStart;
+    }
+    if (contextStart + contextDuration > endFrame) {
+        contextDuration = endFrame - contextStart;
+    }
+
+    long blockFrame = contextStart;
+
+    long prevCompletion = 0;
+
+    setCompletion(0);
+
+    while (!m_abandoned) {
+
+        if (frequencyDomain) {
+            if (blockFrame - int(m_context.blockSize)/2 >
+                contextStart + contextDuration) break;
+        } else {
+            if (blockFrame >= 
+                contextStart + contextDuration) break;
+        }
+
+//	std::cerr << "FeatureExtractionModelTransformer::run: blockFrame "
+//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
+//                  << m_context.blockSize << std::endl;
+
+	long completion =
+	    (((blockFrame - contextStart) / m_context.stepSize) * 99) /
+	    (contextDuration / m_context.stepSize);
+
+	// channelCount is either m_input->channelCount or 1
+
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            if (frequencyDomain) {
+                int column = (blockFrame - startFrame) / m_context.stepSize;
+                for (size_t i = 0; i <= m_context.blockSize/2; ++i) {
+                    fftModels[ch]->getValuesAt
+                        (column, i, buffers[ch][i*2], buffers[ch][i*2+1]);
+                }
+            } else {
+                getFrames(ch, channelCount, 
+                          blockFrame, m_context.blockSize, buffers[ch]);
+            }                
+        }
+
+	Vamp::Plugin::FeatureSet features = m_plugin->process
+	    (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+
+	for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
+	    Vamp::Plugin::Feature feature =
+		features[m_outputFeatureNo][fi];
+	    addFeature(blockFrame, feature);
+	}
+
+	if (blockFrame == contextStart || completion > prevCompletion) {
+	    setCompletion(completion);
+	    prevCompletion = completion;
+	}
+
+	blockFrame += m_context.stepSize;
+    }
+
+    if (m_abandoned) return;
+
+    Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
+
+    for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
+	Vamp::Plugin::Feature feature =
+	    features[m_outputFeatureNo][fi];
+	addFeature(blockFrame, feature);
+    }
+
+    if (frequencyDomain) {
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            delete fftModels[ch];
+        }
+    }
+
+    setCompletion(100);
+}
+
+void
+FeatureExtractionModelTransformer::getFrames(int channel, int channelCount,
+                                            long startFrame, long size,
+                                            float *buffer)
+{
+    long offset = 0;
+
+    if (startFrame < 0) {
+        for (int i = 0; i < size && startFrame + i < 0; ++i) {
+            buffer[i] = 0.0f;
+        }
+        offset = -startFrame;
+        size -= offset;
+        if (size <= 0) return;
+        startFrame = 0;
+    }
+
+    long got = getInput()->getData
+        ((channelCount == 1 ? m_context.channel : channel),
+         startFrame, size, buffer + offset);
+
+    while (got < size) {
+        buffer[offset + got] = 0.0;
+        ++got;
+    }
+
+    if (m_context.channel == -1 && channelCount == 1 &&
+        getInput()->getChannelCount() > 1) {
+        // use mean instead of sum, as plugin input
+        int cc = getInput()->getChannelCount();
+        for (long i = 0; i < size; ++i) {
+            buffer[i] /= cc;
+        }
+    }
+}
+
+void
+FeatureExtractionModelTransformer::addFeature(size_t blockFrame,
+					     const Vamp::Plugin::Feature &feature)
+{
+    size_t inputRate = m_input->getSampleRate();
+
+//    std::cerr << "FeatureExtractionModelTransformer::addFeature("
+//	      << blockFrame << ")" << std::endl;
+
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+    size_t frame = blockFrame;
+
+    if (m_descriptor->sampleType ==
+	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	if (!feature.hasTimestamp) {
+	    std::cerr
+		<< "WARNING: FeatureExtractionModelTransformer::addFeature: "
+		<< "Feature has variable sample rate but no timestamp!"
+		<< std::endl;
+	    return;
+	} else {
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
+	}
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
+
+	if (feature.hasTimestamp) {
+	    //!!! warning: sampleRate may be non-integral
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp,
+                                                   lrintf(m_descriptor->sampleRate));
+	} else {
+	    frame = m_output->getEndFrame();
+	}
+    }
+	
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
+	if (!model) return;
+	model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str()));
+	
+    } else if (binCount == 1) {
+
+	float value = 0.0;
+	if (feature.values.size() > 0) value = feature.values[0];
+
+	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
+	if (!model) return;
+	model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str()));
+//        std::cerr << "SparseTimeValueModel::addPoint(" << frame << ", " << value << "), " << feature.label.c_str() << std::endl;
+
+    } else if (m_descriptor->sampleType == 
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+        float pitch = 0.0;
+        if (feature.values.size() > 0) pitch = feature.values[0];
+
+        float duration = 1;
+        if (feature.values.size() > 1) duration = feature.values[1];
+        
+        float velocity = 100;
+        if (feature.values.size() > 2) velocity = feature.values[2];
+
+        NoteModel *model = getOutput<NoteModel>();
+        if (!model) return;
+
+        model->addPoint(NoteModel::Point(frame, pitch,
+                                         lrintf(duration),
+                                         feature.label.c_str()));
+	
+    } else {
+	
+	DenseThreeDimensionalModel::Column values = feature.values;
+	
+	EditableDenseThreeDimensionalModel *model =
+            getOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+
+	model->setColumn(frame / model->getResolution(), values);
+    }
+}
+
+void
+FeatureExtractionModelTransformer::setCompletion(int completion)
+{
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer::setCompletion("
+//              << completion << ")" << std::endl;
+
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else if (binCount == 1) {
+
+	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	NoteModel *model = getOutput<NoteModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else {
+
+	EditableDenseThreeDimensionalModel *model =
+            getOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionModelTransformer.h	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,62 @@
+/* -*- 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 _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
+#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
+
+#include "PluginTransformer.h"
+
+class DenseTimeValueModel;
+
+class FeatureExtractionModelTransformer : public PluginTransformer
+{
+    Q_OBJECT
+
+public:
+    FeatureExtractionModelTransformer(Model *inputModel,
+                                       QString plugin,
+                                       const ExecutionContext &context,
+                                       QString configurationXml = "",
+                                       QString outputName = "");
+    virtual ~FeatureExtractionModelTransformer();
+
+protected:
+    virtual void run();
+
+    Vamp::Plugin *m_plugin;
+    Vamp::Plugin::OutputDescriptor *m_descriptor;
+    int m_outputFeatureNo;
+
+    void addFeature(size_t blockFrame,
+		    const Vamp::Plugin::Feature &feature);
+
+    void setCompletion(int);
+
+    void getFrames(int channel, int channelCount,
+                   long startFrame, long size, float *buffer);
+
+    // just casts
+    DenseTimeValueModel *getInput();
+    template <typename ModelClass> ModelClass *getOutput() {
+	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
+	if (!mc) {
+	    std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl;
+	}
+	return mc;
+    }
+};
+
+#endif
+
--- a/plugin/transform/FeatureExtractionPluginTransformer.cpp	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,553 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 "FeatureExtractionPluginTransformer.h"
-
-#include "plugin/FeatureExtractionPluginFactory.h"
-#include "plugin/PluginXml.h"
-#include "vamp-sdk/Plugin.h"
-
-#include "data/model/Model.h"
-#include "base/Window.h"
-#include "data/model/SparseOneDimensionalModel.h"
-#include "data/model/SparseTimeValueModel.h"
-#include "data/model/EditableDenseThreeDimensionalModel.h"
-#include "data/model/DenseTimeValueModel.h"
-#include "data/model/NoteModel.h"
-#include "data/model/FFTModel.h"
-#include "data/model/WaveFileModel.h"
-
-#include <QMessageBox>
-
-#include <iostream>
-
-FeatureExtractionPluginTransformer::FeatureExtractionPluginTransformer(Model *inputModel,
-								   QString pluginId,
-                                                                   const ExecutionContext &context,
-                                                                   QString configurationXml,
-								   QString outputName) :
-    PluginTransformer(inputModel, context),
-    m_plugin(0),
-    m_descriptor(0),
-    m_outputFeatureNo(0)
-{
-//    std::cerr << "FeatureExtractionPluginTransformer::FeatureExtractionPluginTransformer: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl;
-
-    FeatureExtractionPluginFactory *factory =
-	FeatureExtractionPluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-	std::cerr << "FeatureExtractionPluginTransformer: No factory available for plugin id \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate());
-
-    if (!m_plugin) {
-	std::cerr << "FeatureExtractionPluginTransformer: Failed to instantiate plugin \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    if (configurationXml != "") {
-        PluginXml(m_plugin).setParametersFromXml(configurationXml);
-    }
-
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    size_t channelCount = input->getChannelCount();
-    if (m_plugin->getMaxChannelCount() < channelCount) {
-	channelCount = 1;
-    }
-    if (m_plugin->getMinChannelCount() > channelCount) {
-	std::cerr << "FeatureExtractionPluginTransformer:: "
-		  << "Can't provide enough channels to plugin (plugin min "
-		  << m_plugin->getMinChannelCount() << ", max "
-		  << m_plugin->getMaxChannelCount() << ", input model has "
-		  << input->getChannelCount() << ")" << std::endl;
-	return;
-    }
-
-    std::cerr << "Initialising feature extraction plugin with channels = "
-              << channelCount << ", step = " << m_context.stepSize
-              << ", block = " << m_context.blockSize << std::endl;
-
-    if (!m_plugin->initialise(channelCount,
-                              m_context.stepSize,
-                              m_context.blockSize)) {
-        std::cerr << "FeatureExtractionPluginTransformer: Plugin "
-                  << m_plugin->getIdentifier() << " failed to initialise!" << std::endl;
-        return;
-    }
-
-    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
-
-    if (outputs.empty()) {
-	std::cerr << "FeatureExtractionPluginTransformer: Plugin \""
-		  << pluginId.toStdString() << "\" has no outputs" << std::endl;
-	return;
-    }
-    
-    for (size_t i = 0; i < outputs.size(); ++i) {
-	if (outputName == "" || outputs[i].identifier == outputName.toStdString()) {
-	    m_outputFeatureNo = i;
-	    m_descriptor = new Vamp::Plugin::OutputDescriptor
-		(outputs[i]);
-	    break;
-	}
-    }
-
-    if (!m_descriptor) {
-	std::cerr << "FeatureExtractionPluginTransformer: Plugin \""
-		  << pluginId.toStdString() << "\" has no output named \""
-		  << outputName.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-//    std::cerr << "FeatureExtractionPluginTransformer: output sample type "
-//	      << m_descriptor->sampleType << std::endl;
-
-    int binCount = 1;
-    float minValue = 0.0, maxValue = 0.0;
-    bool haveExtents = false;
-    
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-//    std::cerr << "FeatureExtractionPluginTransformer: output bin count "
-//	      << binCount << std::endl;
-
-    if (binCount > 0 && m_descriptor->hasKnownExtents) {
-	minValue = m_descriptor->minValue;
-	maxValue = m_descriptor->maxValue;
-        haveExtents = true;
-    }
-
-    size_t modelRate = m_input->getSampleRate();
-    size_t modelResolution = 1;
-    
-    switch (m_descriptor->sampleType) {
-
-    case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
-	if (m_descriptor->sampleRate != 0.0) {
-	    modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001);
-	}
-	break;
-
-    case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
-	modelResolution = m_context.stepSize;
-	break;
-
-    case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
-	modelRate = size_t(m_descriptor->sampleRate + 0.001);
-	break;
-    }
-
-    if (binCount == 0) {
-
-	m_output = new SparseOneDimensionalModel(modelRate, modelResolution,
-						 false);
-
-    } else if (binCount == 1) {
-
-        SparseTimeValueModel *model;
-        if (haveExtents) {
-            model = new SparseTimeValueModel
-                (modelRate, modelResolution, minValue, maxValue, false);
-        } else {
-            model = new SparseTimeValueModel
-                (modelRate, modelResolution, false);
-        }
-        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
-
-        m_output = model;
-
-    } else if (m_descriptor->sampleType ==
-	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-        // We don't have a sparse 3D model, so interpret this as a
-        // note model.  There's nothing to define which values to use
-        // as which parameters of the note -- for the moment let's
-        // treat the first as pitch, second as duration in frames,
-        // third (if present) as velocity. (Our note model doesn't
-        // yet store velocity.)
-        //!!! todo: ask the user!
-	
-        NoteModel *model;
-        if (haveExtents) {
-            model = new NoteModel
-                (modelRate, modelResolution, minValue, maxValue, false);
-        } else {
-            model = new NoteModel
-                (modelRate, modelResolution, false);
-        }            
-        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
-
-        m_output = model;
-
-    } else {
-
-        EditableDenseThreeDimensionalModel *model =
-            new EditableDenseThreeDimensionalModel
-            (modelRate, modelResolution, binCount, false);
-
-	if (!m_descriptor->binNames.empty()) {
-	    std::vector<QString> names;
-	    for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) {
-		names.push_back(m_descriptor->binNames[i].c_str());
-	    }
-	    model->setBinNames(names);
-	}
-        
-        m_output = model;
-    }
-}
-
-FeatureExtractionPluginTransformer::~FeatureExtractionPluginTransformer()
-{
-    std::cerr << "FeatureExtractionPluginTransformer::~FeatureExtractionPluginTransformer()" << std::endl;
-    delete m_plugin;
-    delete m_descriptor;
-}
-
-DenseTimeValueModel *
-FeatureExtractionPluginTransformer::getInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "FeatureExtractionPluginTransformer::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-FeatureExtractionPluginTransformer::run()
-{
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    if (!m_output) return;
-
-    while (!input->isReady()) {
-/*
-        if (dynamic_cast<WaveFileModel *>(input)) {
-            std::cerr << "FeatureExtractionPluginTransformer::run: Model is not ready, but it's not a WaveFileModel (it's a " << typeid(input).name() << "), so that's OK" << std::endl;
-            sleep(2);
-            break; // no need to wait
-        }
-*/
-        std::cerr << "FeatureExtractionPluginTransformer::run: Waiting for input model to be ready..." << std::endl;
-        sleep(1);
-    }
-
-    size_t sampleRate = m_input->getSampleRate();
-
-    size_t channelCount = input->getChannelCount();
-    if (m_plugin->getMaxChannelCount() < channelCount) {
-	channelCount = 1;
-    }
-
-    float **buffers = new float*[channelCount];
-    for (size_t ch = 0; ch < channelCount; ++ch) {
-	buffers[ch] = new float[m_context.blockSize + 2];
-    }
-
-    bool frequencyDomain = (m_plugin->getInputDomain() ==
-                            Vamp::Plugin::FrequencyDomain);
-    std::vector<FFTModel *> fftModels;
-
-    if (frequencyDomain) {
-        for (size_t ch = 0; ch < channelCount; ++ch) {
-            FFTModel *model = new FFTModel
-                                  (getInput(),
-                                   channelCount == 1 ? m_context.channel : ch,
-                                   m_context.windowType,
-                                   m_context.blockSize,
-                                   m_context.stepSize,
-                                   m_context.blockSize,
-                                   false);
-            if (!model->isOK()) {
-                QMessageBox::critical
-                    (0, tr("FFT cache failed"),
-                     tr("Failed to create the FFT model for this transform.\n"
-                        "There may be insufficient memory or disc space to continue."));
-                delete model;
-                setCompletion(100);
-                return;
-            }
-            model->resume();
-            fftModels.push_back(model);
-        }
-    }
-
-    long startFrame = m_input->getStartFrame();
-    long   endFrame = m_input->getEndFrame();
-
-    long contextStart = m_context.startFrame;
-    long contextDuration = m_context.duration;
-
-    if (contextStart == 0 || contextStart < startFrame) {
-        contextStart = startFrame;
-    }
-
-    if (contextDuration == 0) {
-        contextDuration = endFrame - contextStart;
-    }
-    if (contextStart + contextDuration > endFrame) {
-        contextDuration = endFrame - contextStart;
-    }
-
-    long blockFrame = contextStart;
-
-    long prevCompletion = 0;
-
-    setCompletion(0);
-
-    while (!m_abandoned) {
-
-        if (frequencyDomain) {
-            if (blockFrame - int(m_context.blockSize)/2 >
-                contextStart + contextDuration) break;
-        } else {
-            if (blockFrame >= 
-                contextStart + contextDuration) break;
-        }
-
-//	std::cerr << "FeatureExtractionPluginTransformer::run: blockFrame "
-//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
-//                  << m_context.blockSize << std::endl;
-
-	long completion =
-	    (((blockFrame - contextStart) / m_context.stepSize) * 99) /
-	    (contextDuration / m_context.stepSize);
-
-	// channelCount is either m_input->channelCount or 1
-
-        for (size_t ch = 0; ch < channelCount; ++ch) {
-            if (frequencyDomain) {
-                int column = (blockFrame - startFrame) / m_context.stepSize;
-                for (size_t i = 0; i <= m_context.blockSize/2; ++i) {
-                    fftModels[ch]->getValuesAt
-                        (column, i, buffers[ch][i*2], buffers[ch][i*2+1]);
-                }
-            } else {
-                getFrames(ch, channelCount, 
-                          blockFrame, m_context.blockSize, buffers[ch]);
-            }                
-        }
-
-	Vamp::Plugin::FeatureSet features = m_plugin->process
-	    (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
-
-	for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
-	    Vamp::Plugin::Feature feature =
-		features[m_outputFeatureNo][fi];
-	    addFeature(blockFrame, feature);
-	}
-
-	if (blockFrame == contextStart || completion > prevCompletion) {
-	    setCompletion(completion);
-	    prevCompletion = completion;
-	}
-
-	blockFrame += m_context.stepSize;
-    }
-
-    if (m_abandoned) return;
-
-    Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
-
-    for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
-	Vamp::Plugin::Feature feature =
-	    features[m_outputFeatureNo][fi];
-	addFeature(blockFrame, feature);
-    }
-
-    if (frequencyDomain) {
-        for (size_t ch = 0; ch < channelCount; ++ch) {
-            delete fftModels[ch];
-        }
-    }
-
-    setCompletion(100);
-}
-
-void
-FeatureExtractionPluginTransformer::getFrames(int channel, int channelCount,
-                                            long startFrame, long size,
-                                            float *buffer)
-{
-    long offset = 0;
-
-    if (startFrame < 0) {
-        for (int i = 0; i < size && startFrame + i < 0; ++i) {
-            buffer[i] = 0.0f;
-        }
-        offset = -startFrame;
-        size -= offset;
-        if (size <= 0) return;
-        startFrame = 0;
-    }
-
-    long got = getInput()->getData
-        ((channelCount == 1 ? m_context.channel : channel),
-         startFrame, size, buffer + offset);
-
-    while (got < size) {
-        buffer[offset + got] = 0.0;
-        ++got;
-    }
-
-    if (m_context.channel == -1 && channelCount == 1 &&
-        getInput()->getChannelCount() > 1) {
-        // use mean instead of sum, as plugin input
-        int cc = getInput()->getChannelCount();
-        for (long i = 0; i < size; ++i) {
-            buffer[i] /= cc;
-        }
-    }
-}
-
-void
-FeatureExtractionPluginTransformer::addFeature(size_t blockFrame,
-					     const Vamp::Plugin::Feature &feature)
-{
-    size_t inputRate = m_input->getSampleRate();
-
-//    std::cerr << "FeatureExtractionPluginTransformer::addFeature("
-//	      << blockFrame << ")" << std::endl;
-
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-    size_t frame = blockFrame;
-
-    if (m_descriptor->sampleType ==
-	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-	if (!feature.hasTimestamp) {
-	    std::cerr
-		<< "WARNING: FeatureExtractionPluginTransformer::addFeature: "
-		<< "Feature has variable sample rate but no timestamp!"
-		<< std::endl;
-	    return;
-	} else {
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
-	}
-
-    } else if (m_descriptor->sampleType ==
-	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
-
-	if (feature.hasTimestamp) {
-	    //!!! warning: sampleRate may be non-integral
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp,
-                                                   lrintf(m_descriptor->sampleRate));
-	} else {
-	    frame = m_output->getEndFrame();
-	}
-    }
-	
-    if (binCount == 0) {
-
-	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
-	if (!model) return;
-	model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str()));
-	
-    } else if (binCount == 1) {
-
-	float value = 0.0;
-	if (feature.values.size() > 0) value = feature.values[0];
-
-	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
-	if (!model) return;
-	model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str()));
-//        std::cerr << "SparseTimeValueModel::addPoint(" << frame << ", " << value << "), " << feature.label.c_str() << std::endl;
-
-    } else if (m_descriptor->sampleType == 
-	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-        float pitch = 0.0;
-        if (feature.values.size() > 0) pitch = feature.values[0];
-
-        float duration = 1;
-        if (feature.values.size() > 1) duration = feature.values[1];
-        
-        float velocity = 100;
-        if (feature.values.size() > 2) velocity = feature.values[2];
-
-        NoteModel *model = getOutput<NoteModel>();
-        if (!model) return;
-
-        model->addPoint(NoteModel::Point(frame, pitch,
-                                         lrintf(duration),
-                                         feature.label.c_str()));
-	
-    } else {
-	
-	DenseThreeDimensionalModel::Column values = feature.values;
-	
-	EditableDenseThreeDimensionalModel *model =
-            getOutput<EditableDenseThreeDimensionalModel>();
-	if (!model) return;
-
-	model->setColumn(frame / model->getResolution(), values);
-    }
-}
-
-void
-FeatureExtractionPluginTransformer::setCompletion(int completion)
-{
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-//    std::cerr << "FeatureExtractionPluginTransformer::setCompletion("
-//              << completion << ")" << std::endl;
-
-    if (binCount == 0) {
-
-	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
-	if (!model) return;
-	model->setCompletion(completion);
-
-    } else if (binCount == 1) {
-
-	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
-	if (!model) return;
-	model->setCompletion(completion);
-
-    } else if (m_descriptor->sampleType ==
-	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-	NoteModel *model = getOutput<NoteModel>();
-	if (!model) return;
-	model->setCompletion(completion);
-
-    } else {
-
-	EditableDenseThreeDimensionalModel *model =
-            getOutput<EditableDenseThreeDimensionalModel>();
-	if (!model) return;
-	model->setCompletion(completion);
-    }
-}
-
--- a/plugin/transform/FeatureExtractionPluginTransformer.h	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
-#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
-
-#include "PluginTransformer.h"
-
-class DenseTimeValueModel;
-
-class FeatureExtractionPluginTransformer : public PluginTransformer
-{
-    Q_OBJECT
-
-public:
-    FeatureExtractionPluginTransformer(Model *inputModel,
-                                       QString plugin,
-                                       const ExecutionContext &context,
-                                       QString configurationXml = "",
-                                       QString outputName = "");
-    virtual ~FeatureExtractionPluginTransformer();
-
-protected:
-    virtual void run();
-
-    Vamp::Plugin *m_plugin;
-    Vamp::Plugin::OutputDescriptor *m_descriptor;
-    int m_outputFeatureNo;
-
-    void addFeature(size_t blockFrame,
-		    const Vamp::Plugin::Feature &feature);
-
-    void setCompletion(int);
-
-    void getFrames(int channel, int channelCount,
-                   long startFrame, long size, float *buffer);
-
-    // just casts
-    DenseTimeValueModel *getInput();
-    template <typename ModelClass> ModelClass *getOutput() {
-	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
-	if (!mc) {
-	    std::cerr << "FeatureExtractionPluginTransformer::getOutput: Output model not conformable" << std::endl;
-	}
-	return mc;
-    }
-};
-
-#endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/ModelTransformer.cpp	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,32 @@
+/* -*- 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.
+   
+    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 "ModelTransformer.h"
+
+ModelTransformer::ModelTransformer(Model *m) :
+    m_input(m),
+    m_output(0),
+    m_detached(false),
+    m_abandoned(false)
+{
+}
+
+ModelTransformer::~ModelTransformer()
+{
+    m_abandoned = true;
+    wait();
+    if (!m_detached) delete m_output;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/ModelTransformer.h	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,59 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _TRANSFORMER_H_
+#define _TRANSFORMER_H_
+
+#include "base/Thread.h"
+
+#include "data/model/Model.h"
+
+/**
+ * A ModelTransformer turns one data model into another.
+ *
+ * Typically in this application, a ModelTransformer might have a
+ * DenseTimeValueModel as its input (e.g. an audio waveform) and a
+ * SparseOneDimensionalModel (e.g. detected beats) as its output.
+ *
+ * The ModelTransformer typically runs in the background, as a
+ * separate thread populating the output model.  The model is
+ * available to the user of the ModelTransformer immediately, but may
+ * be initially empty until the background thread has populated it.
+ */
+
+class ModelTransformer : public Thread
+{
+public:
+    virtual ~ModelTransformer();
+
+    // Just a hint to the processing thread that it should give up.
+    // Caller should still wait() and/or delete the transform before
+    // assuming its input and output models are no longer required.
+    void abandon() { m_abandoned = true; }
+
+    Model *getInputModel()  { return m_input; }
+    Model *getOutputModel() { return m_output; }
+    Model *detachOutputModel() { m_detached = true; return m_output; }
+
+protected:
+    ModelTransformer(Model *m);
+
+    Model *m_input; // I don't own this
+    Model *m_output; // I own this, unless...
+    bool m_detached; // ... this is true.
+    bool m_abandoned;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/ModelTransformerFactory.cpp	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,867 @@
+/* -*- 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 "ModelTransformerFactory.h"
+
+#include "FeatureExtractionModelTransformer.h"
+#include "RealTimeEffectModelTransformer.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/PluginXml.h"
+
+#include "widgets/PluginParameterDialog.h"
+
+#include "data/model/DenseTimeValueModel.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+
+#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
+
+#include <iostream>
+#include <set>
+
+#include <QRegExp>
+
+ModelTransformerFactory *
+ModelTransformerFactory::m_instance = new ModelTransformerFactory;
+
+ModelTransformerFactory *
+ModelTransformerFactory::getInstance()
+{
+    return m_instance;
+}
+
+ModelTransformerFactory::~ModelTransformerFactory()
+{
+}
+
+TransformList
+ModelTransformerFactory::getAllTransforms()
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<TransformDescription> dset;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+	dset.insert(i->second);
+    }
+
+    TransformList list;
+    for (std::set<TransformDescription>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+	list.push_back(*i);
+    }
+
+    return list;
+}
+
+std::vector<QString>
+ModelTransformerFactory::getAllTransformerTypes()
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> types;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+        types.insert(i->second.type);
+    }
+
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = types.begin(); i != types.end(); ++i) {
+        rv.push_back(*i);
+    }
+
+    return rv;
+}
+
+std::vector<QString>
+ModelTransformerFactory::getTransformerCategories(QString transformType)
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> categories;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
+        if (i->second.type == transformType) {
+            categories.insert(i->second.category);
+        }
+    }
+
+    bool haveEmpty = false;
+    
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = categories.begin(); 
+         i != categories.end(); ++i) {
+        if (*i != "") rv.push_back(*i);
+        else haveEmpty = true;
+    }
+
+    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
+
+    return rv;
+}
+
+std::vector<QString>
+ModelTransformerFactory::getTransformerMakers(QString transformType)
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> makers;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
+        if (i->second.type == transformType) {
+            makers.insert(i->second.maker);
+        }
+    }
+
+    bool haveEmpty = false;
+    
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = makers.begin(); 
+         i != makers.end(); ++i) {
+        if (*i != "") rv.push_back(*i);
+        else haveEmpty = true;
+    }
+
+    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
+
+    return rv;
+}
+
+void
+ModelTransformerFactory::populateTransforms()
+{
+    TransformDescriptionMap transforms;
+
+    populateFeatureExtractionPlugins(transforms);
+    populateRealTimePlugins(transforms);
+
+    // disambiguate plugins with similar names
+
+    std::map<QString, int> names;
+    std::map<QString, QString> pluginSources;
+    std::map<QString, QString> pluginMakers;
+
+    for (TransformDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformDescription desc = i->second;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString pn = desc.identifier.section(":", 1, 1);
+
+        if (pluginSources.find(tn) != pluginSources.end()) {
+            if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) {
+                ++names[tn];
+            }
+        } else {
+            ++names[tn];
+            pluginSources[tn] = pn;
+            pluginMakers[tn] = desc.maker;
+        }
+    }
+
+    std::map<QString, int> counts;
+    m_transforms.clear();
+
+    for (TransformDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformDescription desc = i->second;
+	QString identifier = desc.identifier;
+        QString maker = desc.maker;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString to = td.section(": ", 1);
+
+	if (names[tn] > 1) {
+            maker.replace(QRegExp(tr(" [\\(<].*$")), "");
+	    tn = QString("%1 [%2]").arg(tn).arg(maker);
+	}
+
+        if (to != "") {
+            desc.name = QString("%1: %2").arg(tn).arg(to);
+        } else {
+            desc.name = tn;
+        }
+
+	m_transforms[identifier] = desc;
+    }	    
+}
+
+void
+ModelTransformerFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
+{
+    std::vector<QString> plugs =
+	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
+
+    for (size_t i = 0; i < plugs.size(); ++i) {
+
+	QString pluginId = plugs[i];
+
+	FeatureExtractionPluginFactory *factory =
+	    FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+	if (!factory) {
+	    std::cerr << "WARNING: ModelTransformerFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+
+	Vamp::Plugin *plugin = 
+	    factory->instantiatePlugin(pluginId, 48000);
+
+	if (!plugin) {
+	    std::cerr << "WARNING: ModelTransformerFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+		
+	QString pluginName = plugin->getName().c_str();
+        QString category = factory->getPluginCategory(pluginId);
+
+	Vamp::Plugin::OutputList outputs =
+	    plugin->getOutputDescriptors();
+
+	for (size_t j = 0; j < outputs.size(); ++j) {
+
+	    QString transformId = QString("%1:%2")
+		    .arg(pluginId).arg(outputs[j].identifier.c_str());
+
+	    QString userName;
+            QString friendlyName;
+            QString units = outputs[j].unit.c_str();
+            QString description = plugin->getDescription().c_str();
+            QString maker = plugin->getMaker().c_str();
+            if (maker == "") maker = tr("<unknown maker>");
+
+            if (description == "") {
+                if (outputs.size() == 1) {
+                    description = tr("Extract features using \"%1\" plugin (from %2)")
+                        .arg(pluginName).arg(maker);
+                } else {
+                    description = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)")
+                        .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                }
+            } else {
+                if (outputs.size() == 1) {
+                    description = tr("%1 using \"%2\" plugin (from %3)")
+                        .arg(description).arg(pluginName).arg(maker);
+                } else {
+                    description = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)")
+                        .arg(description).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                }
+            }                    
+
+	    if (outputs.size() == 1) {
+		userName = pluginName;
+                friendlyName = pluginName;
+	    } else {
+		userName = QString("%1: %2")
+		    .arg(pluginName)
+		    .arg(outputs[j].name.c_str());
+                friendlyName = outputs[j].name.c_str();
+	    }
+
+            bool configurable = (!plugin->getPrograms().empty() ||
+                                 !plugin->getParameterDescriptors().empty());
+
+//            std::cerr << "Feature extraction plugin transform: " << transformId.toStdString() << std::endl;
+
+	    transforms[transformId] = 
+                TransformDescription(tr("Analysis"),
+                              category,
+                              transformId,
+                              userName,
+                              friendlyName,
+                              description,
+                              maker,
+                              units,
+                              configurable);
+	}
+
+        delete plugin;
+    }
+}
+
+void
+ModelTransformerFactory::populateRealTimePlugins(TransformDescriptionMap &transforms)
+{
+    std::vector<QString> plugs =
+	RealTimePluginFactory::getAllPluginIdentifiers();
+
+    static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
+
+    for (size_t i = 0; i < plugs.size(); ++i) {
+        
+	QString pluginId = plugs[i];
+
+        RealTimePluginFactory *factory =
+            RealTimePluginFactory::instanceFor(pluginId);
+
+	if (!factory) {
+	    std::cerr << "WARNING: ModelTransformerFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+
+        const RealTimePluginDescriptor *descriptor =
+            factory->getPluginDescriptor(pluginId);
+
+        if (!descriptor) {
+	    std::cerr << "WARNING: ModelTransformerFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+	
+//!!!        if (descriptor->controlOutputPortCount == 0 ||
+//            descriptor->audioInputPortCount == 0) continue;
+
+//        std::cout << "ModelTransformerFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl;
+	
+	QString pluginName = descriptor->name.c_str();
+        QString category = factory->getPluginCategory(pluginId);
+        bool configurable = (descriptor->parameterCount > 0);
+        QString maker = descriptor->maker.c_str();
+        if (maker == "") maker = tr("<unknown maker>");
+
+        if (descriptor->audioInputPortCount > 0) {
+
+            for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
+
+                QString transformId = QString("%1:%2").arg(pluginId).arg(j);
+                QString userName;
+                QString units;
+                QString portName;
+
+                if (j < descriptor->controlOutputPortNames.size() &&
+                    descriptor->controlOutputPortNames[j] != "") {
+
+                    portName = descriptor->controlOutputPortNames[j].c_str();
+
+                    userName = tr("%1: %2")
+                        .arg(pluginName)
+                        .arg(portName);
+
+                    if (unitRE.indexIn(portName) >= 0) {
+                        units = unitRE.cap(1);
+                    }
+
+                } else if (descriptor->controlOutputPortCount > 1) {
+
+                    userName = tr("%1: Output %2")
+                        .arg(pluginName)
+                        .arg(j + 1);
+
+                } else {
+
+                    userName = pluginName;
+                }
+
+                QString description;
+
+                if (portName != "") {
+                    description = tr("Extract \"%1\" data output from \"%2\" effect plugin (from %3)")
+                        .arg(portName)
+                        .arg(pluginName)
+                        .arg(maker);
+                } else {
+                    description = tr("Extract data output %1 from \"%2\" effect plugin (from %3)")
+                        .arg(j + 1)
+                        .arg(pluginName)
+                        .arg(maker);
+                }
+
+                transforms[transformId] = 
+                    TransformDescription(tr("Effects Data"),
+                                  category,
+                                  transformId,
+                                  userName,
+                                  userName,
+                                  description,
+                                  maker,
+                                  units,
+                                  configurable);
+            }
+        }
+
+        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
+
+            if (descriptor->audioOutputPortCount > 0) {
+
+                QString transformId = QString("%1:A").arg(pluginId);
+                QString type = tr("Effects");
+
+                QString description = tr("Transformer audio signal with \"%1\" effect plugin (from %2)")
+                    .arg(pluginName)
+                    .arg(maker);
+
+                if (descriptor->audioInputPortCount == 0) {
+                    type = tr("Generators");
+                    QString description = tr("Generate audio signal using \"%1\" plugin (from %2)")
+                        .arg(pluginName)
+                        .arg(maker);
+                }
+
+                transforms[transformId] =
+                    TransformDescription(type,
+                                  category,
+                                  transformId,
+                                  pluginName,
+                                  pluginName,
+                                  description,
+                                  maker,
+                                  "",
+                                  configurable);
+            }
+        }
+    }
+}
+
+bool
+ModelTransformerFactory::haveTransformer(TransformId identifier)
+{
+    return (m_transforms.find(identifier) != m_transforms.end());
+}
+
+QString
+ModelTransformerFactory::getTransformerName(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].name;
+    } else return "";
+}
+
+QString
+ModelTransformerFactory::getTransformerFriendlyName(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].friendlyName;
+    } else return "";
+}
+
+QString
+ModelTransformerFactory::getTransformerUnits(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].units;
+    } else return "";
+}
+
+bool
+ModelTransformerFactory::isTransformerConfigurable(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].configurable;
+    } else return false;
+}
+
+bool
+ModelTransformerFactory::getTransformerChannelRange(TransformId identifier,
+                                           int &min, int &max)
+{
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *plugin = 
+            FeatureExtractionPluginFactory::instanceFor(id)->
+            instantiatePlugin(id, 48000);
+        if (!plugin) return false;
+
+        min = plugin->getMinChannelCount();
+        max = plugin->getMaxChannelCount();
+        delete plugin;
+
+        return true;
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        const RealTimePluginDescriptor *descriptor = 
+            RealTimePluginFactory::instanceFor(id)->
+            getPluginDescriptor(id);
+        if (!descriptor) return false;
+
+        min = descriptor->audioInputPortCount;
+        max = descriptor->audioInputPortCount;
+
+        return true;
+    }
+
+    return false;
+}
+
+bool
+ModelTransformerFactory::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin,
+                                  int &minChannels, int &maxChannels)
+{
+    Vamp::Plugin *vp = 0;
+    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
+        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
+        minChannels = vp->getMinChannelCount();
+        maxChannels = vp->getMaxChannelCount();
+        return true;
+    } else {
+        return getTransformerChannelRange(identifier, minChannels, maxChannels);
+    }
+}
+
+Model *
+ModelTransformerFactory::getConfigurationForTransformer(TransformId identifier,
+                                               const std::vector<Model *> &candidateInputModels,
+                                               PluginTransformer::ExecutionContext &context,
+                                               QString &configurationXml,
+                                               AudioCallbackPlaySource *source,
+                                               size_t startFrame,
+                                               size_t duration)
+{
+    if (candidateInputModels.empty()) return 0;
+
+    //!!! This will need revision -- we'll have to have a callback
+    //from the dialog for when the candidate input model is changed,
+    //as we'll need to reinitialise the channel settings in the dialog
+    Model *inputModel = candidateInputModels[0]; //!!! for now
+    QStringList candidateModelNames;
+    std::map<QString, Model *> modelMap;
+    for (size_t i = 0; i < candidateInputModels.size(); ++i) {
+        QString modelName = candidateInputModels[i]->objectName();
+        QString origModelName = modelName;
+        int dupcount = 1;
+        while (modelMap.find(modelName) != modelMap.end()) {
+            modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
+        }
+        modelMap[modelName] = candidateInputModels[i];
+        candidateModelNames.push_back(modelName);
+    }
+
+    QString id = identifier.section(':', 0, 2);
+    QString output = identifier.section(':', 3);
+    QString outputLabel = "";
+    QString outputDescription = "";
+    
+    bool ok = false;
+    configurationXml = m_lastConfigurations[identifier];
+
+//    std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
+
+    Vamp::PluginBase *plugin = 0;
+
+    bool frequency = false;
+    bool effect = false;
+    bool generator = false;
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        std::cerr << "getConfigurationForTransformer: instantiating Vamp plugin" << std::endl;
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel->getSampleRate());
+
+        if (vp) {
+
+            plugin = vp;
+            frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
+
+            std::vector<Vamp::Plugin::OutputDescriptor> od =
+                vp->getOutputDescriptors();
+            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)) {
+
+        RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
+        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
+
+        if (desc->audioInputPortCount > 0 && 
+            desc->audioOutputPortCount > 0 &&
+            !desc->isSynth) {
+            effect = true;
+        }
+
+        if (desc->audioInputPortCount == 0) {
+            generator = true;
+        }
+
+        if (output != "A") {
+            int outputNo = output.toInt();
+            if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
+                outputLabel = desc->controlOutputPortNames[outputNo].c_str();
+            }
+        }
+
+        size_t sampleRate = inputModel->getSampleRate();
+        size_t blockSize = 1024;
+        size_t channels = 1;
+        if (effect && source) {
+            sampleRate = source->getTargetSampleRate();
+            blockSize = source->getTargetBlockSize();
+            channels = source->getTargetChannelCount();
+        }
+
+        RealTimePluginInstance *rtp = factory->instantiatePlugin
+            (id, 0, 0, sampleRate, blockSize, channels);
+
+        plugin = rtp;
+
+        if (effect && source && rtp) {
+            source->setAuditioningPlugin(rtp);
+        }
+    }
+
+    if (plugin) {
+
+        context = PluginTransformer::ExecutionContext(context.channel, plugin);
+
+        if (configurationXml != "") {
+            PluginXml(plugin).setParametersFromXml(configurationXml);
+        }
+
+        int sourceChannels = 1;
+        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
+            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
+                ->getChannelCount();
+        }
+
+        int minChannels = 1, maxChannels = sourceChannels;
+        getChannelRange(identifier, plugin, minChannels, maxChannels);
+
+        int targetChannels = sourceChannels;
+        if (!effect) {
+            if (sourceChannels < minChannels) targetChannels = minChannels;
+            if (sourceChannels > maxChannels) targetChannels = maxChannels;
+        }
+
+        int defaultChannel = context.channel;
+
+        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
+
+        if (candidateModelNames.size() > 1 && !generator) {
+            dialog->setCandidateInputModels(candidateModelNames);
+        }
+
+        if (startFrame != 0 || duration != 0) {
+            dialog->setShowSelectionOnlyOption(true);
+        }
+
+        if (targetChannels > 0) {
+            dialog->setChannelArrangement(sourceChannels, targetChannels,
+                                          defaultChannel);
+        }
+        
+        dialog->setOutputLabel(outputLabel, outputDescription);
+        
+        dialog->setShowProcessingOptions(true, frequency);
+
+        if (dialog->exec() == QDialog::Accepted) {
+            ok = true;
+        }
+
+        QString selectedInput = dialog->getInputModel();
+        if (selectedInput != "") {
+            if (modelMap.find(selectedInput) != modelMap.end()) {
+                inputModel = modelMap[selectedInput];
+                std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
+            } else {
+                std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
+            }
+        } else {
+            std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
+        }
+
+        configurationXml = PluginXml(plugin).toXmlString();
+        context.channel = dialog->getChannel();
+        
+        if (startFrame != 0 || duration != 0) {
+            if (dialog->getSelectionOnly()) {
+                context.startFrame = startFrame;
+                context.duration = duration;
+            }
+        }
+
+        dialog->getProcessingParameters(context.stepSize,
+                                        context.blockSize,
+                                        context.windowType);
+
+        context.makeConsistentWithPlugin(plugin);
+
+        delete dialog;
+
+        if (effect && source) {
+            source->setAuditioningPlugin(0); // will delete our plugin
+        } else {
+            delete plugin;
+        }
+    }
+
+    if (ok) m_lastConfigurations[identifier] = configurationXml;
+
+    return ok ? inputModel : 0;
+}
+
+PluginTransformer::ExecutionContext
+ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
+                                                Model *inputModel)
+{
+    PluginTransformer::ExecutionContext context(-1);
+
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel ? inputModel->getSampleRate() : 48000);
+
+        if (vp) {
+            context = PluginTransformer::ExecutionContext(-1, vp);
+            delete vp;
+        }
+    }
+
+    return context;
+}
+
+ModelTransformer *
+ModelTransformerFactory::createTransformer(TransformId identifier, Model *inputModel,
+                                  const PluginTransformer::ExecutionContext &context,
+                                  QString configurationXml)
+{
+    ModelTransformer *transformer = 0;
+
+    QString id = identifier.section(':', 0, 2);
+    QString output = identifier.section(':', 3);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+        transformer = new FeatureExtractionModelTransformer(inputModel,
+                                                            id,
+                                                            context,
+                                                            configurationXml,
+                                                            output);
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+        transformer = new RealTimeEffectModelTransformer(inputModel,
+                                                         id,
+                                                         context,
+                                                         configurationXml,
+                                                         getTransformerUnits(identifier),
+                                                         output == "A" ? -1 :
+                                                         output.toInt());
+    } else {
+        std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
+                  << identifier.toStdString() << "\"" << std::endl;
+        return transformer;
+    }
+
+    if (transformer) transformer->setObjectName(identifier);
+    return transformer;
+}
+
+Model *
+ModelTransformerFactory::transform(TransformId identifier, Model *inputModel,
+                            const PluginTransformer::ExecutionContext &context,
+                            QString configurationXml)
+{
+    ModelTransformer *t = createTransformer(identifier, inputModel, context,
+                                            configurationXml);
+
+    if (!t) return 0;
+
+    connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
+
+    m_runningTransformers.insert(t);
+
+    t->start();
+    Model *model = t->detachOutputModel();
+
+    if (model) {
+        QString imn = inputModel->objectName();
+        QString trn = getTransformerFriendlyName(identifier);
+        if (imn != "") {
+            if (trn != "") {
+                model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
+            } else {
+                model->setObjectName(imn);
+            }
+        } else if (trn != "") {
+            model->setObjectName(trn);
+        }
+    } else {
+        t->wait();
+    }
+
+    return model;
+}
+
+void
+ModelTransformerFactory::transformerFinished()
+{
+    QObject *s = sender();
+    ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
+    
+    std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
+
+    if (!transformer) {
+	std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
+	return;
+    }
+
+    if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
+        std::cerr << "WARNING: ModelTransformerFactory::transformerFinished(" 
+                  << transformer
+                  << "): I have no record of this transformer running!"
+                  << std::endl;
+    }
+
+    m_runningTransformers.erase(transformer);
+
+    transformer->wait(); // unnecessary but reassuring
+    delete transformer;
+}
+
+void
+ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
+{
+    TransformerSet affected;
+
+    for (TransformerSet::iterator i = m_runningTransformers.begin();
+         i != m_runningTransformers.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        if (t->getInputModel() == m || t->getOutputModel() == m) {
+            affected.insert(t);
+        }
+    }
+
+    for (TransformerSet::iterator i = affected.begin();
+         i != affected.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        t->abandon();
+
+        t->wait(); // this should eventually call back on
+                   // transformerFinished, which will remove from
+                   // m_runningTransformers and delete.
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/ModelTransformerFactory.h	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,156 @@
+/* -*- 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 _MODEL_TRANSFORMER_FACTORY_H_
+#define _MODEL_TRANSFORMER_FACTORY_H_
+
+#include "Transform.h"
+#include "TransformDescription.h"
+
+#include "ModelTransformer.h"
+
+#include "PluginTransformer.h"
+
+#include <map>
+#include <set>
+
+namespace Vamp { class PluginBase; }
+
+class AudioCallbackPlaySource;
+
+//!!! split into TransformFactory (information about available
+// transforms, create default Transform for each transform ID etc) and
+// TransformerFactory (create Transformers to apply transforms)
+
+class ModelTransformerFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~ModelTransformerFactory();
+
+    static ModelTransformerFactory *getInstance();
+
+    TransformList getAllTransforms();
+
+    std::vector<QString> getAllTransformerTypes();
+
+    std::vector<QString> getTransformerCategories(QString transformType);
+    std::vector<QString> getTransformerMakers(QString transformType);
+
+    /**
+     * Get a configuration XML string for the given transform (by
+     * asking the user, most likely).  Returns the selected input
+     * model if the transform is acceptable, 0 if the operation should
+     * be cancelled.  Audio callback play source may be used to
+     * audition effects plugins, if provided.
+     */
+    Model *getConfigurationForTransformer(TransformId identifier,
+                                        const std::vector<Model *> &candidateInputModels,
+                                        PluginTransformer::ExecutionContext &context,
+                                        QString &configurationXml,
+                                        AudioCallbackPlaySource *source = 0,
+                                        size_t startFrame = 0,
+                                        size_t duration = 0);
+
+    /**
+     * Get the default execution context for the given transform
+     * and input model (if known).
+     */
+    PluginTransformer::ExecutionContext getDefaultContextForTransformer(TransformId identifier,
+                                                                    Model *inputModel = 0);
+
+    /**
+     * 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.
+     *
+     * If the transform is unknown or the input model is not an
+     * appropriate type for the given transform, or if some other
+     * problem occurs, return 0.
+     * 
+     * The returned model is owned by the caller and must be deleted
+     * when no longer needed.
+     */
+    Model *transform(TransformId identifier, Model *inputModel,
+                     const PluginTransformer::ExecutionContext &context,
+                     QString configurationXml = "");
+
+    /**
+     * Return true if the given transform is known.
+     */
+    bool haveTransformer(TransformId identifier);
+
+    /**
+     * Full name of a transform, suitable for putting on a menu.
+     */
+    QString getTransformerName(TransformId identifier);
+
+    /**
+     * Brief but friendly name of a transform, suitable for use
+     * as the name of the output layer.
+     */
+    QString getTransformerFriendlyName(TransformId identifier);
+
+    QString getTransformerUnits(TransformId identifier);
+
+    /**
+     * Return true if the transform has any configurable parameters,
+     * i.e. if getConfigurationForTransformer can ever return a non-trivial
+     * (not equivalent to empty) configuration string.
+     */
+    bool isTransformerConfigurable(TransformId identifier);
+
+    /**
+     * If the transform has a prescribed number or range of channel
+     * inputs, return true and set minChannels and maxChannels to the
+     * minimum and maximum number of channel inputs the transform can
+     * accept.  Return false if it doesn't care.
+     */
+    bool getTransformerChannelRange(TransformId identifier,
+                                  int &minChannels, int &maxChannels);
+	
+protected slots:
+    void transformerFinished();
+
+    void modelAboutToBeDeleted(Model *);
+
+protected:
+    ModelTransformer *createTransformer(TransformId identifier, Model *inputModel,
+                                        const PluginTransformer::ExecutionContext &context,
+                                        QString configurationXml);
+
+    typedef std::map<TransformId, QString> TransformerConfigurationMap;
+    TransformerConfigurationMap m_lastConfigurations;
+
+    typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
+    TransformDescriptionMap m_transforms;
+
+    typedef std::set<ModelTransformer *> TransformerSet;
+    TransformerSet m_runningTransformers;
+
+    void populateTransforms();
+    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
+    void populateRealTimePlugins(TransformDescriptionMap &);
+
+    bool getChannelRange(TransformId identifier,
+                         Vamp::PluginBase *plugin, int &min, int &max);
+
+    static ModelTransformerFactory *m_instance;
+};
+
+
+#endif
--- a/plugin/transform/PluginTransformer.cpp	Tue Nov 06 17:15:00 2007 +0000
+++ b/plugin/transform/PluginTransformer.cpp	Wed Nov 07 12:59:01 2007 +0000
@@ -20,7 +20,7 @@
 
 PluginTransformer::PluginTransformer(Model *inputModel,
 				 const ExecutionContext &context) :
-    Transformer(inputModel),
+    ModelTransformer(inputModel),
     m_context(context)
 {
 }
--- a/plugin/transform/PluginTransformer.h	Tue Nov 06 17:15:00 2007 +0000
+++ b/plugin/transform/PluginTransformer.h	Wed Nov 07 12:59:01 2007 +0000
@@ -16,7 +16,7 @@
 #ifndef _PLUGIN_TRANSFORMER_H_
 #define _PLUGIN_TRANSFORMER_H_
 
-#include "Transformer.h"
+#include "ModelTransformer.h"
 
 #include "base/Window.h"
 
@@ -25,7 +25,7 @@
 //!!! should this just move back up to Transformer? It is after all used
 //directly in all sorts of generic places, like Document
 
-class PluginTransformer : public Transformer
+class PluginTransformer : public ModelTransformer
 {
 public:
     class ExecutionContext {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimeEffectModelTransformer.cpp	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,274 @@
+/* -*- 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 "RealTimeEffectModelTransformer.h"
+
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/RealTimePluginInstance.h"
+#include "plugin/PluginXml.h"
+
+#include "data/model/Model.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/DenseTimeValueModel.h"
+#include "data/model/WritableWaveFileModel.h"
+#include "data/model/WaveFileModel.h"
+
+#include <iostream>
+
+RealTimeEffectModelTransformer::RealTimeEffectModelTransformer(Model *inputModel,
+                                                               QString pluginId,
+                                                               const ExecutionContext &context,
+                                                               QString configurationXml,
+                                                               QString units,
+                                                               int output) :
+    PluginTransformer(inputModel, context),
+    m_pluginId(pluginId),
+    m_configurationXml(configurationXml),
+    m_units(units),
+    m_plugin(0),
+    m_outputNo(output)
+{
+    if (!m_context.blockSize) m_context.blockSize = 1024;
+
+//    std::cerr << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
+
+    RealTimePluginFactory *factory =
+	RealTimePluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+	std::cerr << "RealTimeEffectModelTransformer: No factory available for plugin id \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
+                                          m_input->getSampleRate(),
+                                          m_context.blockSize,
+                                          input->getChannelCount());
+
+    if (!m_plugin) {
+	std::cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    if (configurationXml != "") {
+        PluginXml(m_plugin).setParametersFromXml(configurationXml);
+    }
+
+    if (m_outputNo >= 0 &&
+        m_outputNo >= int(m_plugin->getControlOutputCount())) {
+        std::cerr << "RealTimeEffectModelTransformer: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
+        return;
+    }
+
+    if (m_outputNo == -1) {
+
+        size_t outputChannels = m_plugin->getAudioOutputCount();
+        if (outputChannels > input->getChannelCount()) {
+            outputChannels = input->getChannelCount();
+        }
+
+        WritableWaveFileModel *model = new WritableWaveFileModel
+            (input->getSampleRate(), outputChannels);
+
+        m_output = model;
+
+    } else {
+	
+        SparseTimeValueModel *model = new SparseTimeValueModel
+            (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false);
+
+        if (units != "") model->setScaleUnits(units);
+
+        m_output = model;
+    }
+}
+
+RealTimeEffectModelTransformer::~RealTimeEffectModelTransformer()
+{
+    delete m_plugin;
+}
+
+DenseTimeValueModel *
+RealTimeEffectModelTransformer::getInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "RealTimeEffectModelTransformer::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+RealTimeEffectModelTransformer::run()
+{
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    while (!input->isReady()) {
+        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
+        std::cerr << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
+    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
+    if (!stvm && !wwfm) return;
+
+    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
+
+    size_t sampleRate = input->getSampleRate();
+    size_t channelCount = input->getChannelCount();
+    if (!wwfm && m_context.channel != -1) channelCount = 1;
+
+    long blockSize = m_plugin->getBufferSize();
+
+    float **inbufs = m_plugin->getAudioInputBuffers();
+
+    long startFrame = m_input->getStartFrame();
+    long   endFrame = m_input->getEndFrame();
+    
+    long contextStart = m_context.startFrame;
+    long contextDuration = m_context.duration;
+
+    if (contextStart == 0 || contextStart < startFrame) {
+        contextStart = startFrame;
+    }
+
+    if (contextDuration == 0) {
+        contextDuration = endFrame - contextStart;
+    }
+    if (contextStart + contextDuration > endFrame) {
+        contextDuration = endFrame - contextStart;
+    }
+
+    wwfm->setStartFrame(contextStart);
+
+    long blockFrame = contextStart;
+
+    long prevCompletion = 0;
+
+    long latency = m_plugin->getLatency();
+
+    while (blockFrame < contextStart + contextDuration + latency &&
+           !m_abandoned) {
+
+	long completion =
+	    (((blockFrame - contextStart) / blockSize) * 99) /
+	    ((contextDuration) / blockSize);
+
+	long got = 0;
+
+	if (channelCount == 1) {
+            if (inbufs && inbufs[0]) {
+                got = input->getData
+                    (m_context.channel, blockFrame, blockSize, inbufs[0]);
+                while (got < blockSize) {
+                    inbufs[0][got++] = 0.0;
+                }          
+            }
+            for (size_t ch = 1; ch < m_plugin->getAudioInputCount(); ++ch) {
+                for (long i = 0; i < blockSize; ++i) {
+                    inbufs[ch][i] = inbufs[0][i];
+                }
+            }
+	} else {
+	    for (size_t ch = 0; ch < channelCount; ++ch) {
+                if (inbufs && inbufs[ch]) {
+                    got = input->getData
+                        (ch, blockFrame, blockSize, inbufs[ch]);
+                    while (got < blockSize) {
+                        inbufs[ch][got++] = 0.0;
+                    }
+                }
+	    }
+            for (size_t ch = channelCount; ch < m_plugin->getAudioInputCount(); ++ch) {
+                for (long i = 0; i < blockSize; ++i) {
+                    inbufs[ch][i] = inbufs[ch % channelCount][i];
+                }
+            }
+	}
+
+/*
+        std::cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< std::endl;
+
+        for (size_t ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) {
+            std::cerr << "Input channel " << ch << std::endl;
+            for (size_t i = 0; i < 100; ++i) {
+                std::cerr << inbufs[ch][i] << " ";
+                if (isnan(inbufs[ch][i])) {
+                    std::cerr << "\n\nWARNING: NaN in audio input" << std::endl;
+                }
+            }
+        }
+*/
+
+        m_plugin->run(Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+
+        if (stvm) {
+
+            float value = m_plugin->getControlOutputValue(m_outputNo);
+
+            long pointFrame = blockFrame;
+            if (pointFrame > latency) pointFrame -= latency;
+            else pointFrame = 0;
+
+            stvm->addPoint(SparseTimeValueModel::Point
+                           (pointFrame, value, ""));
+
+        } else if (wwfm) {
+
+            float **outbufs = m_plugin->getAudioOutputBuffers();
+
+            if (outbufs) {
+
+                if (blockFrame >= latency) {
+                    long writeSize = std::min
+                        (blockSize,
+                         contextStart + contextDuration + latency - blockFrame);
+                    wwfm->addSamples(outbufs, writeSize);
+                } else if (blockFrame + blockSize >= latency) {
+                    long offset = latency - blockFrame;
+                    long count = blockSize - offset;
+                    float **tmp = new float *[channelCount];
+                    for (size_t c = 0; c < channelCount; ++c) {
+                        tmp[c] = outbufs[c] + offset;
+                    }
+                    wwfm->addSamples(tmp, count);
+                    delete[] tmp;
+                }
+            }
+        }
+
+	if (blockFrame == contextStart || completion > prevCompletion) {
+	    if (stvm) stvm->setCompletion(completion);
+	    if (wwfm) wwfm->setCompletion(completion);
+	    prevCompletion = completion;
+	}
+        
+	blockFrame += blockSize;
+    }
+
+    if (m_abandoned) return;
+    
+    if (stvm) stvm->setCompletion(100);
+    if (wwfm) wwfm->setCompletion(100);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimeEffectModelTransformer.h	Wed Nov 07 12:59:01 2007 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This 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 _REAL_TIME_PLUGIN_TRANSFORMER_H_
+#define _REAL_TIME_PLUGIN_TRANSFORMER_H_
+
+#include "PluginTransformer.h"
+#include "plugin/RealTimePluginInstance.h"
+
+class DenseTimeValueModel;
+
+class RealTimeEffectModelTransformer : public PluginTransformer
+{
+public:
+    RealTimeEffectModelTransformer(Model *inputModel,
+			    QString plugin,
+                            const ExecutionContext &context,
+			    QString configurationXml = "",
+                            QString units = "",
+			    int output = -1); // -1 -> audio, 0+ -> data
+    virtual ~RealTimeEffectModelTransformer();
+
+protected:
+    virtual void run();
+
+    QString m_pluginId;
+    QString m_configurationXml;
+    QString m_units;
+
+    RealTimePluginInstance *m_plugin;
+    int m_outputNo;
+
+    // just casts
+    DenseTimeValueModel *getInput();
+};
+
+#endif
+
--- a/plugin/transform/RealTimePluginTransformer.cpp	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 "RealTimePluginTransformer.h"
-
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/RealTimePluginInstance.h"
-#include "plugin/PluginXml.h"
-
-#include "data/model/Model.h"
-#include "data/model/SparseTimeValueModel.h"
-#include "data/model/DenseTimeValueModel.h"
-#include "data/model/WritableWaveFileModel.h"
-#include "data/model/WaveFileModel.h"
-
-#include <iostream>
-
-RealTimePluginTransformer::RealTimePluginTransformer(Model *inputModel,
-                                                 QString pluginId,
-                                                 const ExecutionContext &context,
-                                                 QString configurationXml,
-                                                 QString units,
-                                                 int output) :
-    PluginTransformer(inputModel, context),
-    m_pluginId(pluginId),
-    m_configurationXml(configurationXml),
-    m_units(units),
-    m_plugin(0),
-    m_outputNo(output)
-{
-    if (!m_context.blockSize) m_context.blockSize = 1024;
-
-//    std::cerr << "RealTimePluginTransformer::RealTimePluginTransformer: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
-
-    RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-	std::cerr << "RealTimePluginTransformer: No factory available for plugin id \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
-                                          m_input->getSampleRate(),
-                                          m_context.blockSize,
-                                          input->getChannelCount());
-
-    if (!m_plugin) {
-	std::cerr << "RealTimePluginTransformer: Failed to instantiate plugin \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    if (configurationXml != "") {
-        PluginXml(m_plugin).setParametersFromXml(configurationXml);
-    }
-
-    if (m_outputNo >= 0 &&
-        m_outputNo >= int(m_plugin->getControlOutputCount())) {
-        std::cerr << "RealTimePluginTransformer: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
-        return;
-    }
-
-    if (m_outputNo == -1) {
-
-        size_t outputChannels = m_plugin->getAudioOutputCount();
-        if (outputChannels > input->getChannelCount()) {
-            outputChannels = input->getChannelCount();
-        }
-
-        WritableWaveFileModel *model = new WritableWaveFileModel
-            (input->getSampleRate(), outputChannels);
-
-        m_output = model;
-
-    } else {
-	
-        SparseTimeValueModel *model = new SparseTimeValueModel
-            (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false);
-
-        if (units != "") model->setScaleUnits(units);
-
-        m_output = model;
-    }
-}
-
-RealTimePluginTransformer::~RealTimePluginTransformer()
-{
-    delete m_plugin;
-}
-
-DenseTimeValueModel *
-RealTimePluginTransformer::getInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "RealTimePluginTransformer::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-RealTimePluginTransformer::run()
-{
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    while (!input->isReady()) {
-        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
-        std::cerr << "RealTimePluginTransformer::run: Waiting for input model to be ready..." << std::endl;
-        sleep(1);
-    }
-
-    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
-    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
-    if (!stvm && !wwfm) return;
-
-    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
-
-    size_t sampleRate = input->getSampleRate();
-    size_t channelCount = input->getChannelCount();
-    if (!wwfm && m_context.channel != -1) channelCount = 1;
-
-    long blockSize = m_plugin->getBufferSize();
-
-    float **inbufs = m_plugin->getAudioInputBuffers();
-
-    long startFrame = m_input->getStartFrame();
-    long   endFrame = m_input->getEndFrame();
-    
-    long contextStart = m_context.startFrame;
-    long contextDuration = m_context.duration;
-
-    if (contextStart == 0 || contextStart < startFrame) {
-        contextStart = startFrame;
-    }
-
-    if (contextDuration == 0) {
-        contextDuration = endFrame - contextStart;
-    }
-    if (contextStart + contextDuration > endFrame) {
-        contextDuration = endFrame - contextStart;
-    }
-
-    wwfm->setStartFrame(contextStart);
-
-    long blockFrame = contextStart;
-
-    long prevCompletion = 0;
-
-    long latency = m_plugin->getLatency();
-
-    while (blockFrame < contextStart + contextDuration + latency &&
-           !m_abandoned) {
-
-	long completion =
-	    (((blockFrame - contextStart) / blockSize) * 99) /
-	    ((contextDuration) / blockSize);
-
-	long got = 0;
-
-	if (channelCount == 1) {
-            if (inbufs && inbufs[0]) {
-                got = input->getData
-                    (m_context.channel, blockFrame, blockSize, inbufs[0]);
-                while (got < blockSize) {
-                    inbufs[0][got++] = 0.0;
-                }          
-            }
-            for (size_t ch = 1; ch < m_plugin->getAudioInputCount(); ++ch) {
-                for (long i = 0; i < blockSize; ++i) {
-                    inbufs[ch][i] = inbufs[0][i];
-                }
-            }
-	} else {
-	    for (size_t ch = 0; ch < channelCount; ++ch) {
-                if (inbufs && inbufs[ch]) {
-                    got = input->getData
-                        (ch, blockFrame, blockSize, inbufs[ch]);
-                    while (got < blockSize) {
-                        inbufs[ch][got++] = 0.0;
-                    }
-                }
-	    }
-            for (size_t ch = channelCount; ch < m_plugin->getAudioInputCount(); ++ch) {
-                for (long i = 0; i < blockSize; ++i) {
-                    inbufs[ch][i] = inbufs[ch % channelCount][i];
-                }
-            }
-	}
-
-/*
-        std::cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< std::endl;
-
-        for (size_t ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) {
-            std::cerr << "Input channel " << ch << std::endl;
-            for (size_t i = 0; i < 100; ++i) {
-                std::cerr << inbufs[ch][i] << " ";
-                if (isnan(inbufs[ch][i])) {
-                    std::cerr << "\n\nWARNING: NaN in audio input" << std::endl;
-                }
-            }
-        }
-*/
-
-        m_plugin->run(Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
-
-        if (stvm) {
-
-            float value = m_plugin->getControlOutputValue(m_outputNo);
-
-            long pointFrame = blockFrame;
-            if (pointFrame > latency) pointFrame -= latency;
-            else pointFrame = 0;
-
-            stvm->addPoint(SparseTimeValueModel::Point
-                           (pointFrame, value, ""));
-
-        } else if (wwfm) {
-
-            float **outbufs = m_plugin->getAudioOutputBuffers();
-
-            if (outbufs) {
-
-                if (blockFrame >= latency) {
-                    long writeSize = std::min
-                        (blockSize,
-                         contextStart + contextDuration + latency - blockFrame);
-                    wwfm->addSamples(outbufs, writeSize);
-                } else if (blockFrame + blockSize >= latency) {
-                    long offset = latency - blockFrame;
-                    long count = blockSize - offset;
-                    float **tmp = new float *[channelCount];
-                    for (size_t c = 0; c < channelCount; ++c) {
-                        tmp[c] = outbufs[c] + offset;
-                    }
-                    wwfm->addSamples(tmp, count);
-                    delete[] tmp;
-                }
-            }
-        }
-
-	if (blockFrame == contextStart || completion > prevCompletion) {
-	    if (stvm) stvm->setCompletion(completion);
-	    if (wwfm) wwfm->setCompletion(completion);
-	    prevCompletion = completion;
-	}
-        
-	blockFrame += blockSize;
-    }
-
-    if (m_abandoned) return;
-    
-    if (stvm) stvm->setCompletion(100);
-    if (wwfm) wwfm->setCompletion(100);
-}
-
--- a/plugin/transform/RealTimePluginTransformer.h	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 _REAL_TIME_PLUGIN_TRANSFORMER_H_
-#define _REAL_TIME_PLUGIN_TRANSFORMER_H_
-
-#include "PluginTransformer.h"
-#include "plugin/RealTimePluginInstance.h"
-
-class DenseTimeValueModel;
-
-class RealTimePluginTransformer : public PluginTransformer
-{
-public:
-    RealTimePluginTransformer(Model *inputModel,
-			    QString plugin,
-                            const ExecutionContext &context,
-			    QString configurationXml = "",
-                            QString units = "",
-			    int output = -1); // -1 -> audio, 0+ -> data
-    virtual ~RealTimePluginTransformer();
-
-protected:
-    virtual void run();
-
-    QString m_pluginId;
-    QString m_configurationXml;
-    QString m_units;
-
-    RealTimePluginInstance *m_plugin;
-    int m_outputNo;
-
-    // just casts
-    DenseTimeValueModel *getInput();
-};
-
-#endif
-
--- a/plugin/transform/Transformer.cpp	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-   
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "Transformer.h"
-
-Transformer::Transformer(Model *m) :
-    m_input(m),
-    m_output(0),
-    m_detached(false),
-    m_abandoned(false)
-{
-}
-
-Transformer::~Transformer()
-{
-    m_abandoned = true;
-    wait();
-    if (!m_detached) delete m_output;
-}
-
--- a/plugin/transform/Transformer.h	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-   
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _TRANSFORMER_H_
-#define _TRANSFORMER_H_
-
-#include "base/Thread.h"
-
-#include "data/model/Model.h"
-
-typedef QString TransformerId;
-
-/**
- * A Transformer turns one data model into another.
- *
- * Typically in this application, a Transformer might have a
- * DenseTimeValueModel as its input (e.g. an audio waveform) and a
- * SparseOneDimensionalModel (e.g. detected beats) as its output.
- *
- * The Transformer typically runs in the background, as a separate
- * thread populating the output model.  The model is available to the
- * user of the Transformer immediately, but may be initially empty until
- * the background thread has populated it.
- */
-
-class Transformer : public Thread
-{
-public:
-    virtual ~Transformer();
-
-    // Just a hint to the processing thread that it should give up.
-    // Caller should still wait() and/or delete the transform before
-    // assuming its input and output models are no longer required.
-    void abandon() { m_abandoned = true; }
-
-    Model *getInputModel()  { return m_input; }
-    Model *getOutputModel() { return m_output; }
-    Model *detachOutputModel() { m_detached = true; return m_output; }
-
-protected:
-    Transformer(Model *m);
-
-    Model *m_input; // I don't own this
-    Model *m_output; // I own this, unless...
-    bool m_detached; // ... this is true.
-    bool m_abandoned;
-};
-
-#endif
--- a/plugin/transform/TransformerFactory.cpp	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,867 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 "TransformerFactory.h"
-
-#include "FeatureExtractionPluginTransformer.h"
-#include "RealTimePluginTransformer.h"
-
-#include "plugin/FeatureExtractionPluginFactory.h"
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/PluginXml.h"
-
-#include "widgets/PluginParameterDialog.h"
-
-#include "data/model/DenseTimeValueModel.h"
-
-#include "vamp-sdk/PluginHostAdapter.h"
-
-#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
-
-#include <iostream>
-#include <set>
-
-#include <QRegExp>
-
-TransformerFactory *
-TransformerFactory::m_instance = new TransformerFactory;
-
-TransformerFactory *
-TransformerFactory::getInstance()
-{
-    return m_instance;
-}
-
-TransformerFactory::~TransformerFactory()
-{
-}
-
-TransformList
-TransformerFactory::getAllTransforms()
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<TransformDescription> dset;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
-	dset.insert(i->second);
-    }
-
-    TransformList list;
-    for (std::set<TransformDescription>::const_iterator i = dset.begin();
-	 i != dset.end(); ++i) {
-	list.push_back(*i);
-    }
-
-    return list;
-}
-
-std::vector<QString>
-TransformerFactory::getAllTransformerTypes()
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<QString> types;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
-        types.insert(i->second.type);
-    }
-
-    std::vector<QString> rv;
-    for (std::set<QString>::iterator i = types.begin(); i != types.end(); ++i) {
-        rv.push_back(*i);
-    }
-
-    return rv;
-}
-
-std::vector<QString>
-TransformerFactory::getTransformerCategories(QString transformType)
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<QString> categories;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-         i != m_transforms.end(); ++i) {
-        if (i->second.type == transformType) {
-            categories.insert(i->second.category);
-        }
-    }
-
-    bool haveEmpty = false;
-    
-    std::vector<QString> rv;
-    for (std::set<QString>::iterator i = categories.begin(); 
-         i != categories.end(); ++i) {
-        if (*i != "") rv.push_back(*i);
-        else haveEmpty = true;
-    }
-
-    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
-
-    return rv;
-}
-
-std::vector<QString>
-TransformerFactory::getTransformerMakers(QString transformType)
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<QString> makers;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-         i != m_transforms.end(); ++i) {
-        if (i->second.type == transformType) {
-            makers.insert(i->second.maker);
-        }
-    }
-
-    bool haveEmpty = false;
-    
-    std::vector<QString> rv;
-    for (std::set<QString>::iterator i = makers.begin(); 
-         i != makers.end(); ++i) {
-        if (*i != "") rv.push_back(*i);
-        else haveEmpty = true;
-    }
-
-    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
-
-    return rv;
-}
-
-void
-TransformerFactory::populateTransforms()
-{
-    TransformDescriptionMap transforms;
-
-    populateFeatureExtractionPlugins(transforms);
-    populateRealTimePlugins(transforms);
-
-    // disambiguate plugins with similar names
-
-    std::map<QString, int> names;
-    std::map<QString, QString> pluginSources;
-    std::map<QString, QString> pluginMakers;
-
-    for (TransformDescriptionMap::iterator i = transforms.begin();
-         i != transforms.end(); ++i) {
-
-        TransformDescription desc = i->second;
-
-        QString td = desc.name;
-        QString tn = td.section(": ", 0, 0);
-        QString pn = desc.identifier.section(":", 1, 1);
-
-        if (pluginSources.find(tn) != pluginSources.end()) {
-            if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) {
-                ++names[tn];
-            }
-        } else {
-            ++names[tn];
-            pluginSources[tn] = pn;
-            pluginMakers[tn] = desc.maker;
-        }
-    }
-
-    std::map<QString, int> counts;
-    m_transforms.clear();
-
-    for (TransformDescriptionMap::iterator i = transforms.begin();
-         i != transforms.end(); ++i) {
-
-        TransformDescription desc = i->second;
-	QString identifier = desc.identifier;
-        QString maker = desc.maker;
-
-        QString td = desc.name;
-        QString tn = td.section(": ", 0, 0);
-        QString to = td.section(": ", 1);
-
-	if (names[tn] > 1) {
-            maker.replace(QRegExp(tr(" [\\(<].*$")), "");
-	    tn = QString("%1 [%2]").arg(tn).arg(maker);
-	}
-
-        if (to != "") {
-            desc.name = QString("%1: %2").arg(tn).arg(to);
-        } else {
-            desc.name = tn;
-        }
-
-	m_transforms[identifier] = desc;
-    }	    
-}
-
-void
-TransformerFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
-{
-    std::vector<QString> plugs =
-	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
-
-    for (size_t i = 0; i < plugs.size(); ++i) {
-
-	QString pluginId = plugs[i];
-
-	FeatureExtractionPluginFactory *factory =
-	    FeatureExtractionPluginFactory::instanceFor(pluginId);
-
-	if (!factory) {
-	    std::cerr << "WARNING: TransformerFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
-	    continue;
-	}
-
-	Vamp::Plugin *plugin = 
-	    factory->instantiatePlugin(pluginId, 48000);
-
-	if (!plugin) {
-	    std::cerr << "WARNING: TransformerFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl;
-	    continue;
-	}
-		
-	QString pluginName = plugin->getName().c_str();
-        QString category = factory->getPluginCategory(pluginId);
-
-	Vamp::Plugin::OutputList outputs =
-	    plugin->getOutputDescriptors();
-
-	for (size_t j = 0; j < outputs.size(); ++j) {
-
-	    QString transformId = QString("%1:%2")
-		    .arg(pluginId).arg(outputs[j].identifier.c_str());
-
-	    QString userName;
-            QString friendlyName;
-            QString units = outputs[j].unit.c_str();
-            QString description = plugin->getDescription().c_str();
-            QString maker = plugin->getMaker().c_str();
-            if (maker == "") maker = tr("<unknown maker>");
-
-            if (description == "") {
-                if (outputs.size() == 1) {
-                    description = tr("Extract features using \"%1\" plugin (from %2)")
-                        .arg(pluginName).arg(maker);
-                } else {
-                    description = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)")
-                        .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
-                }
-            } else {
-                if (outputs.size() == 1) {
-                    description = tr("%1 using \"%2\" plugin (from %3)")
-                        .arg(description).arg(pluginName).arg(maker);
-                } else {
-                    description = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)")
-                        .arg(description).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
-                }
-            }                    
-
-	    if (outputs.size() == 1) {
-		userName = pluginName;
-                friendlyName = pluginName;
-	    } else {
-		userName = QString("%1: %2")
-		    .arg(pluginName)
-		    .arg(outputs[j].name.c_str());
-                friendlyName = outputs[j].name.c_str();
-	    }
-
-            bool configurable = (!plugin->getPrograms().empty() ||
-                                 !plugin->getParameterDescriptors().empty());
-
-//            std::cerr << "Feature extraction plugin transform: " << transformId.toStdString() << std::endl;
-
-	    transforms[transformId] = 
-                TransformDescription(tr("Analysis"),
-                              category,
-                              transformId,
-                              userName,
-                              friendlyName,
-                              description,
-                              maker,
-                              units,
-                              configurable);
-	}
-
-        delete plugin;
-    }
-}
-
-void
-TransformerFactory::populateRealTimePlugins(TransformDescriptionMap &transforms)
-{
-    std::vector<QString> plugs =
-	RealTimePluginFactory::getAllPluginIdentifiers();
-
-    static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
-
-    for (size_t i = 0; i < plugs.size(); ++i) {
-        
-	QString pluginId = plugs[i];
-
-        RealTimePluginFactory *factory =
-            RealTimePluginFactory::instanceFor(pluginId);
-
-	if (!factory) {
-	    std::cerr << "WARNING: TransformerFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
-	    continue;
-	}
-
-        const RealTimePluginDescriptor *descriptor =
-            factory->getPluginDescriptor(pluginId);
-
-        if (!descriptor) {
-	    std::cerr << "WARNING: TransformerFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl;
-	    continue;
-	}
-	
-//!!!        if (descriptor->controlOutputPortCount == 0 ||
-//            descriptor->audioInputPortCount == 0) continue;
-
-//        std::cout << "TransformerFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl;
-	
-	QString pluginName = descriptor->name.c_str();
-        QString category = factory->getPluginCategory(pluginId);
-        bool configurable = (descriptor->parameterCount > 0);
-        QString maker = descriptor->maker.c_str();
-        if (maker == "") maker = tr("<unknown maker>");
-
-        if (descriptor->audioInputPortCount > 0) {
-
-            for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
-
-                QString transformId = QString("%1:%2").arg(pluginId).arg(j);
-                QString userName;
-                QString units;
-                QString portName;
-
-                if (j < descriptor->controlOutputPortNames.size() &&
-                    descriptor->controlOutputPortNames[j] != "") {
-
-                    portName = descriptor->controlOutputPortNames[j].c_str();
-
-                    userName = tr("%1: %2")
-                        .arg(pluginName)
-                        .arg(portName);
-
-                    if (unitRE.indexIn(portName) >= 0) {
-                        units = unitRE.cap(1);
-                    }
-
-                } else if (descriptor->controlOutputPortCount > 1) {
-
-                    userName = tr("%1: Output %2")
-                        .arg(pluginName)
-                        .arg(j + 1);
-
-                } else {
-
-                    userName = pluginName;
-                }
-
-                QString description;
-
-                if (portName != "") {
-                    description = tr("Extract \"%1\" data output from \"%2\" effect plugin (from %3)")
-                        .arg(portName)
-                        .arg(pluginName)
-                        .arg(maker);
-                } else {
-                    description = tr("Extract data output %1 from \"%2\" effect plugin (from %3)")
-                        .arg(j + 1)
-                        .arg(pluginName)
-                        .arg(maker);
-                }
-
-                transforms[transformId] = 
-                    TransformDescription(tr("Effects Data"),
-                                  category,
-                                  transformId,
-                                  userName,
-                                  userName,
-                                  description,
-                                  maker,
-                                  units,
-                                  configurable);
-            }
-        }
-
-        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
-
-            if (descriptor->audioOutputPortCount > 0) {
-
-                QString transformId = QString("%1:A").arg(pluginId);
-                QString type = tr("Effects");
-
-                QString description = tr("Transformer audio signal with \"%1\" effect plugin (from %2)")
-                    .arg(pluginName)
-                    .arg(maker);
-
-                if (descriptor->audioInputPortCount == 0) {
-                    type = tr("Generators");
-                    QString description = tr("Generate audio signal using \"%1\" plugin (from %2)")
-                        .arg(pluginName)
-                        .arg(maker);
-                }
-
-                transforms[transformId] =
-                    TransformDescription(type,
-                                  category,
-                                  transformId,
-                                  pluginName,
-                                  pluginName,
-                                  description,
-                                  maker,
-                                  "",
-                                  configurable);
-            }
-        }
-    }
-}
-
-bool
-TransformerFactory::haveTransformer(TransformId identifier)
-{
-    return (m_transforms.find(identifier) != m_transforms.end());
-}
-
-QString
-TransformerFactory::getTransformerName(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].name;
-    } else return "";
-}
-
-QString
-TransformerFactory::getTransformerFriendlyName(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].friendlyName;
-    } else return "";
-}
-
-QString
-TransformerFactory::getTransformerUnits(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].units;
-    } else return "";
-}
-
-bool
-TransformerFactory::isTransformerConfigurable(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].configurable;
-    } else return false;
-}
-
-bool
-TransformerFactory::getTransformerChannelRange(TransformId identifier,
-                                           int &min, int &max)
-{
-    QString id = identifier.section(':', 0, 2);
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        Vamp::Plugin *plugin = 
-            FeatureExtractionPluginFactory::instanceFor(id)->
-            instantiatePlugin(id, 48000);
-        if (!plugin) return false;
-
-        min = plugin->getMinChannelCount();
-        max = plugin->getMaxChannelCount();
-        delete plugin;
-
-        return true;
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-
-        const RealTimePluginDescriptor *descriptor = 
-            RealTimePluginFactory::instanceFor(id)->
-            getPluginDescriptor(id);
-        if (!descriptor) return false;
-
-        min = descriptor->audioInputPortCount;
-        max = descriptor->audioInputPortCount;
-
-        return true;
-    }
-
-    return false;
-}
-
-bool
-TransformerFactory::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin,
-                                  int &minChannels, int &maxChannels)
-{
-    Vamp::Plugin *vp = 0;
-    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
-        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
-        minChannels = vp->getMinChannelCount();
-        maxChannels = vp->getMaxChannelCount();
-        return true;
-    } else {
-        return getTransformerChannelRange(identifier, minChannels, maxChannels);
-    }
-}
-
-Model *
-TransformerFactory::getConfigurationForTransformer(TransformId identifier,
-                                               const std::vector<Model *> &candidateInputModels,
-                                               PluginTransformer::ExecutionContext &context,
-                                               QString &configurationXml,
-                                               AudioCallbackPlaySource *source,
-                                               size_t startFrame,
-                                               size_t duration)
-{
-    if (candidateInputModels.empty()) return 0;
-
-    //!!! This will need revision -- we'll have to have a callback
-    //from the dialog for when the candidate input model is changed,
-    //as we'll need to reinitialise the channel settings in the dialog
-    Model *inputModel = candidateInputModels[0]; //!!! for now
-    QStringList candidateModelNames;
-    std::map<QString, Model *> modelMap;
-    for (size_t i = 0; i < candidateInputModels.size(); ++i) {
-        QString modelName = candidateInputModels[i]->objectName();
-        QString origModelName = modelName;
-        int dupcount = 1;
-        while (modelMap.find(modelName) != modelMap.end()) {
-            modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
-        }
-        modelMap[modelName] = candidateInputModels[i];
-        candidateModelNames.push_back(modelName);
-    }
-
-    QString id = identifier.section(':', 0, 2);
-    QString output = identifier.section(':', 3);
-    QString outputLabel = "";
-    QString outputDescription = "";
-    
-    bool ok = false;
-    configurationXml = m_lastConfigurations[identifier];
-
-//    std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
-
-    Vamp::PluginBase *plugin = 0;
-
-    bool frequency = false;
-    bool effect = false;
-    bool generator = false;
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        std::cerr << "getConfigurationForTransformer: instantiating Vamp plugin" << std::endl;
-
-        Vamp::Plugin *vp =
-            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, inputModel->getSampleRate());
-
-        if (vp) {
-
-            plugin = vp;
-            frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
-
-            std::vector<Vamp::Plugin::OutputDescriptor> od =
-                vp->getOutputDescriptors();
-            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)) {
-
-        RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
-        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
-
-        if (desc->audioInputPortCount > 0 && 
-            desc->audioOutputPortCount > 0 &&
-            !desc->isSynth) {
-            effect = true;
-        }
-
-        if (desc->audioInputPortCount == 0) {
-            generator = true;
-        }
-
-        if (output != "A") {
-            int outputNo = output.toInt();
-            if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
-                outputLabel = desc->controlOutputPortNames[outputNo].c_str();
-            }
-        }
-
-        size_t sampleRate = inputModel->getSampleRate();
-        size_t blockSize = 1024;
-        size_t channels = 1;
-        if (effect && source) {
-            sampleRate = source->getTargetSampleRate();
-            blockSize = source->getTargetBlockSize();
-            channels = source->getTargetChannelCount();
-        }
-
-        RealTimePluginInstance *rtp = factory->instantiatePlugin
-            (id, 0, 0, sampleRate, blockSize, channels);
-
-        plugin = rtp;
-
-        if (effect && source && rtp) {
-            source->setAuditioningPlugin(rtp);
-        }
-    }
-
-    if (plugin) {
-
-        context = PluginTransformer::ExecutionContext(context.channel, plugin);
-
-        if (configurationXml != "") {
-            PluginXml(plugin).setParametersFromXml(configurationXml);
-        }
-
-        int sourceChannels = 1;
-        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
-            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
-                ->getChannelCount();
-        }
-
-        int minChannels = 1, maxChannels = sourceChannels;
-        getChannelRange(identifier, plugin, minChannels, maxChannels);
-
-        int targetChannels = sourceChannels;
-        if (!effect) {
-            if (sourceChannels < minChannels) targetChannels = minChannels;
-            if (sourceChannels > maxChannels) targetChannels = maxChannels;
-        }
-
-        int defaultChannel = context.channel;
-
-        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
-
-        if (candidateModelNames.size() > 1 && !generator) {
-            dialog->setCandidateInputModels(candidateModelNames);
-        }
-
-        if (startFrame != 0 || duration != 0) {
-            dialog->setShowSelectionOnlyOption(true);
-        }
-
-        if (targetChannels > 0) {
-            dialog->setChannelArrangement(sourceChannels, targetChannels,
-                                          defaultChannel);
-        }
-        
-        dialog->setOutputLabel(outputLabel, outputDescription);
-        
-        dialog->setShowProcessingOptions(true, frequency);
-
-        if (dialog->exec() == QDialog::Accepted) {
-            ok = true;
-        }
-
-        QString selectedInput = dialog->getInputModel();
-        if (selectedInput != "") {
-            if (modelMap.find(selectedInput) != modelMap.end()) {
-                inputModel = modelMap[selectedInput];
-                std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
-            } else {
-                std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
-            }
-        } else {
-            std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
-        }
-
-        configurationXml = PluginXml(plugin).toXmlString();
-        context.channel = dialog->getChannel();
-        
-        if (startFrame != 0 || duration != 0) {
-            if (dialog->getSelectionOnly()) {
-                context.startFrame = startFrame;
-                context.duration = duration;
-            }
-        }
-
-        dialog->getProcessingParameters(context.stepSize,
-                                        context.blockSize,
-                                        context.windowType);
-
-        context.makeConsistentWithPlugin(plugin);
-
-        delete dialog;
-
-        if (effect && source) {
-            source->setAuditioningPlugin(0); // will delete our plugin
-        } else {
-            delete plugin;
-        }
-    }
-
-    if (ok) m_lastConfigurations[identifier] = configurationXml;
-
-    return ok ? inputModel : 0;
-}
-
-PluginTransformer::ExecutionContext
-TransformerFactory::getDefaultContextForTransformer(TransformId identifier,
-                                                Model *inputModel)
-{
-    PluginTransformer::ExecutionContext context(-1);
-
-    QString id = identifier.section(':', 0, 2);
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        Vamp::Plugin *vp =
-            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, inputModel ? inputModel->getSampleRate() : 48000);
-
-        if (vp) {
-            context = PluginTransformer::ExecutionContext(-1, vp);
-            delete vp;
-        }
-    }
-
-    return context;
-}
-
-Transformer *
-TransformerFactory::createTransformer(TransformId identifier, Model *inputModel,
-                                  const PluginTransformer::ExecutionContext &context,
-                                  QString configurationXml)
-{
-    Transformer *transform = 0;
-
-    QString id = identifier.section(':', 0, 2);
-    QString output = identifier.section(':', 3);
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-        transform = new FeatureExtractionPluginTransformer(inputModel,
-                                                         id,
-                                                         context,
-                                                         configurationXml,
-                                                         output);
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-        transform = new RealTimePluginTransformer(inputModel,
-                                                id,
-                                                context,
-                                                configurationXml,
-                                                getTransformerUnits(identifier),
-                                                output == "A" ? -1 :
-                                                output.toInt());
-    } else {
-        std::cerr << "TransformerFactory::createTransformer: Unknown transform \""
-                  << identifier.toStdString() << "\"" << std::endl;
-        return transform;
-    }
-
-    if (transform) transform->setObjectName(identifier);
-    return transform;
-}
-
-Model *
-TransformerFactory::transform(TransformId identifier, Model *inputModel,
-                            const PluginTransformer::ExecutionContext &context,
-                            QString configurationXml)
-{
-    Transformer *t = createTransformer(identifier, inputModel, context,
-                                   configurationXml);
-
-    if (!t) return 0;
-
-    connect(t, SIGNAL(finished()), this, SLOT(transformFinished()));
-
-    m_runningTransformers.insert(t);
-
-    t->start();
-    Model *model = t->detachOutputModel();
-
-    if (model) {
-        QString imn = inputModel->objectName();
-        QString trn = getTransformerFriendlyName(identifier);
-        if (imn != "") {
-            if (trn != "") {
-                model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
-            } else {
-                model->setObjectName(imn);
-            }
-        } else if (trn != "") {
-            model->setObjectName(trn);
-        }
-    } else {
-        t->wait();
-    }
-
-    return model;
-}
-
-void
-TransformerFactory::transformFinished()
-{
-    QObject *s = sender();
-    Transformer *transform = dynamic_cast<Transformer *>(s);
-    
-    std::cerr << "TransformerFactory::transformFinished(" << transform << ")" << std::endl;
-
-    if (!transform) {
-	std::cerr << "WARNING: TransformerFactory::transformFinished: sender is not a transform" << std::endl;
-	return;
-    }
-
-    if (m_runningTransformers.find(transform) == m_runningTransformers.end()) {
-        std::cerr << "WARNING: TransformerFactory::transformFinished(" 
-                  << transform
-                  << "): I have no record of this transform running!"
-                  << std::endl;
-    }
-
-    m_runningTransformers.erase(transform);
-
-    transform->wait(); // unnecessary but reassuring
-    delete transform;
-}
-
-void
-TransformerFactory::modelAboutToBeDeleted(Model *m)
-{
-    TransformerSet affected;
-
-    for (TransformerSet::iterator i = m_runningTransformers.begin();
-         i != m_runningTransformers.end(); ++i) {
-
-        Transformer *t = *i;
-
-        if (t->getInputModel() == m || t->getOutputModel() == m) {
-            affected.insert(t);
-        }
-    }
-
-    for (TransformerSet::iterator i = affected.begin();
-         i != affected.end(); ++i) {
-
-        Transformer *t = *i;
-
-        t->abandon();
-
-        t->wait(); // this should eventually call back on
-                   // transformFinished, which will remove from
-                   // m_runningTransformers and delete.
-    }
-}
-
--- a/plugin/transform/TransformerFactory.h	Tue Nov 06 17:15:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 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 _TRANSFORMER_FACTORY_H_
-#define _TRANSFORMER_FACTORY_H_
-
-#include "TransformDescription.h"
-
-#include "Transformer.h"
-#include "PluginTransformer.h"
-
-#include <map>
-#include <set>
-
-namespace Vamp { class PluginBase; }
-
-class AudioCallbackPlaySource;
-
-//!!! split into TransformFactory (information about available
-// transforms, create default Transform for each transform ID etc) and
-// TransformerFactory (create Transformers to apply transforms)
-
-class TransformerFactory : public QObject
-{
-    Q_OBJECT
-
-public:
-    virtual ~TransformerFactory();
-
-    static TransformerFactory *getInstance();
-
-    TransformList getAllTransforms();
-
-    std::vector<QString> getAllTransformerTypes();
-
-    std::vector<QString> getTransformerCategories(QString transformType);
-    std::vector<QString> getTransformerMakers(QString transformType);
-
-    /**
-     * Get a configuration XML string for the given transform (by
-     * asking the user, most likely).  Returns the selected input
-     * model if the transform is acceptable, 0 if the operation should
-     * be cancelled.  Audio callback play source may be used to
-     * audition effects plugins, if provided.
-     */
-    Model *getConfigurationForTransformer(TransformId identifier,
-                                        const std::vector<Model *> &candidateInputModels,
-                                        PluginTransformer::ExecutionContext &context,
-                                        QString &configurationXml,
-                                        AudioCallbackPlaySource *source = 0,
-                                        size_t startFrame = 0,
-                                        size_t duration = 0);
-
-    /**
-     * Get the default execution context for the given transform
-     * and input model (if known).
-     */
-    PluginTransformer::ExecutionContext getDefaultContextForTransformer(TransformId identifier,
-                                                                    Model *inputModel = 0);
-
-    /**
-     * 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.
-     *
-     * If the transform is unknown or the input model is not an
-     * appropriate type for the given transform, or if some other
-     * problem occurs, return 0.
-     * 
-     * The returned model is owned by the caller and must be deleted
-     * when no longer needed.
-     */
-    Model *transform(TransformId identifier, Model *inputModel,
-                     const PluginTransformer::ExecutionContext &context,
-                     QString configurationXml = "");
-
-    /**
-     * Return true if the given transform is known.
-     */
-    bool haveTransformer(TransformId identifier);
-
-    /**
-     * Full name of a transform, suitable for putting on a menu.
-     */
-    QString getTransformerName(TransformId identifier);
-
-    /**
-     * Brief but friendly name of a transform, suitable for use
-     * as the name of the output layer.
-     */
-    QString getTransformerFriendlyName(TransformId identifier);
-
-    QString getTransformerUnits(TransformId identifier);
-
-    /**
-     * Return true if the transform has any configurable parameters,
-     * i.e. if getConfigurationForTransformer can ever return a non-trivial
-     * (not equivalent to empty) configuration string.
-     */
-    bool isTransformerConfigurable(TransformId identifier);
-
-    /**
-     * If the transform has a prescribed number or range of channel
-     * inputs, return true and set minChannels and maxChannels to the
-     * minimum and maximum number of channel inputs the transform can
-     * accept.  Return false if it doesn't care.
-     */
-    bool getTransformerChannelRange(TransformId identifier,
-                                  int &minChannels, int &maxChannels);
-	
-protected slots:
-    void transformFinished();
-
-    void modelAboutToBeDeleted(Model *);
-
-protected:
-    Transformer *createTransformer(TransformId identifier, Model *inputModel,
-                               const PluginTransformer::ExecutionContext &context,
-                               QString configurationXml);
-
-    struct TransformIdent
-    {
-        TransformId identifier;
-        QString configurationXml;
-    };
-
-    typedef std::map<TransformId, QString> TransformerConfigurationMap;
-    TransformerConfigurationMap m_lastConfigurations;
-
-    typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
-    TransformDescriptionMap m_transforms;
-
-    typedef std::set<Transformer *> TransformerSet;
-    TransformerSet m_runningTransformers;
-
-    void populateTransforms();
-    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
-    void populateRealTimePlugins(TransformDescriptionMap &);
-
-    bool getChannelRange(TransformId identifier,
-                         Vamp::PluginBase *plugin, int &min, int &max);
-
-    static TransformerFactory *m_instance;
-};
-
-
-#endif