changeset 328:21bd032ae791

* Introduce new Transform class which contains data necessary to describe the context for a plugin -- the plugin's name and output, the step/block size etc (formerly spread across ExecutionContext and TransformFactory). Other code hasn't been updated to use this yet. * Rename existing Transform stuff to Transformers (because they run Transforms) I'm still not 100% sure about this change, don't rely on it.
author Chris Cannam
date Mon, 05 Nov 2007 15:31:06 +0000
parents 1d656dcda8ef
children 3179d8b29336
files plugin/plugin.pro plugin/transform/FeatureExtractionPluginTransform.cpp plugin/transform/FeatureExtractionPluginTransform.h plugin/transform/FeatureExtractionPluginTransformer.cpp plugin/transform/FeatureExtractionPluginTransformer.h plugin/transform/PluginTransform.cpp plugin/transform/PluginTransform.h plugin/transform/PluginTransformer.cpp plugin/transform/PluginTransformer.h plugin/transform/RealTimePluginTransform.cpp plugin/transform/RealTimePluginTransform.h plugin/transform/RealTimePluginTransformer.cpp plugin/transform/RealTimePluginTransformer.h plugin/transform/Transform.cpp plugin/transform/Transform.h plugin/transform/TransformFactory.cpp plugin/transform/TransformFactory.h plugin/transform/Transformer.cpp plugin/transform/Transformer.h plugin/transform/TransformerFactory.cpp plugin/transform/TransformerFactory.h
diffstat 21 files changed, 2410 insertions(+), 2225 deletions(-) [+]
line wrap: on
line diff
--- a/plugin/plugin.pro	Fri Nov 02 16:50:31 2007 +0000
+++ b/plugin/plugin.pro	Mon Nov 05 15:31:06 2007 +0000
@@ -35,11 +35,12 @@
            api/alsa/seq_event.h \
            api/alsa/seq_midi_event.h \
            api/alsa/sound/asequencer.h \
-           transform/FeatureExtractionPluginTransform.h \
-           transform/PluginTransform.h \
-           transform/RealTimePluginTransform.h \
+           transform/FeatureExtractionPluginTransformer.h \
+           transform/PluginTransformer.h \
+           transform/RealTimePluginTransformer.h \
            transform/Transform.h \
-           transform/TransformFactory.h
+           transform/Transformer.h \
+           transform/TransformerFactory.h
 SOURCES += DSSIPluginFactory.cpp \
            DSSIPluginInstance.cpp \
            FeatureExtractionPluginFactory.cpp \
@@ -51,8 +52,9 @@
            RealTimePluginInstance.cpp \
            api/dssi_alsa_compat.c \
            plugins/SamplePlayer.cpp \
-           transform/FeatureExtractionPluginTransform.cpp \
-           transform/PluginTransform.cpp \
-           transform/RealTimePluginTransform.cpp \
+           transform/FeatureExtractionPluginTransformer.cpp \
+           transform/PluginTransformer.cpp \
+           transform/RealTimePluginTransformer.cpp \
            transform/Transform.cpp \
-           transform/TransformFactory.cpp
+           transform/Transformer.cpp \
+           transform/TransformerFactory.cpp
--- a/plugin/transform/FeatureExtractionPluginTransform.cpp	Fri Nov 02 16:50:31 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 "FeatureExtractionPluginTransform.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>
-
-FeatureExtractionPluginTransform::FeatureExtractionPluginTransform(Model *inputModel,
-								   QString pluginId,
-                                                                   const ExecutionContext &context,
-                                                                   QString configurationXml,
-								   QString outputName) :
-    PluginTransform(inputModel, context),
-    m_plugin(0),
-    m_descriptor(0),
-    m_outputFeatureNo(0)
-{
-//    std::cerr << "FeatureExtractionPluginTransform::FeatureExtractionPluginTransform: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl;
-
-    FeatureExtractionPluginFactory *factory =
-	FeatureExtractionPluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-	std::cerr << "FeatureExtractionPluginTransform: No factory available for plugin id \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate());
-
-    if (!m_plugin) {
-	std::cerr << "FeatureExtractionPluginTransform: 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 << "FeatureExtractionPluginTransform:: "
-		  << "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 << "FeatureExtractionPluginTransform: Plugin "
-                  << m_plugin->getIdentifier() << " failed to initialise!" << std::endl;
-        return;
-    }
-
-    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
-
-    if (outputs.empty()) {
-	std::cerr << "FeatureExtractionPluginTransform: 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 << "FeatureExtractionPluginTransform: Plugin \""
-		  << pluginId.toStdString() << "\" has no output named \""
-		  << outputName.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-//    std::cerr << "FeatureExtractionPluginTransform: 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 << "FeatureExtractionPluginTransform: 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;
-    }
-}
-
-FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform()
-{
-    std::cerr << "FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform()" << std::endl;
-    delete m_plugin;
-    delete m_descriptor;
-}
-
-DenseTimeValueModel *
-FeatureExtractionPluginTransform::getInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "FeatureExtractionPluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-FeatureExtractionPluginTransform::run()
-{
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    if (!m_output) return;
-
-    while (!input->isReady()) {
-/*
-        if (dynamic_cast<WaveFileModel *>(input)) {
-            std::cerr << "FeatureExtractionPluginTransform::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 << "FeatureExtractionPluginTransform::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 << "FeatureExtractionPluginTransform::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
-FeatureExtractionPluginTransform::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
-FeatureExtractionPluginTransform::addFeature(size_t blockFrame,
-					     const Vamp::Plugin::Feature &feature)
-{
-    size_t inputRate = m_input->getSampleRate();
-
-//    std::cerr << "FeatureExtractionPluginTransform::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: FeatureExtractionPluginTransform::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
-FeatureExtractionPluginTransform::setCompletion(int completion)
-{
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-//    std::cerr << "FeatureExtractionPluginTransform::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/FeatureExtractionPluginTransform.h	Fri Nov 02 16:50:31 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_TRANSFORM_H_
-#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORM_H_
-
-#include "PluginTransform.h"
-
-class DenseTimeValueModel;
-
-class FeatureExtractionPluginTransform : public PluginTransform
-{
-    Q_OBJECT
-
-public:
-    FeatureExtractionPluginTransform(Model *inputModel,
-				     QString plugin,
-                                     const ExecutionContext &context,
-                                     QString configurationXml = "",
-				     QString outputName = "");
-    virtual ~FeatureExtractionPluginTransform();
-
-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 << "FeatureExtractionPluginTransform::getOutput: Output model not conformable" << std::endl;
-	}
-	return mc;
-    }
-};
-
-#endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionPluginTransformer.cpp	Mon Nov 05 15:31:06 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 "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);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionPluginTransformer.h	Mon Nov 05 15:31:06 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 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
+
--- a/plugin/transform/PluginTransform.cpp	Fri Nov 02 16:50:31 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +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 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 "PluginTransform.h"
-
-#include "vamp-sdk/PluginHostAdapter.h"
-#include "vamp-sdk/hostext/PluginWrapper.h"
-
-PluginTransform::PluginTransform(Model *inputModel,
-				 const ExecutionContext &context) :
-    Transform(inputModel),
-    m_context(context)
-{
-}
-
-PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _bs) :
-    channel(_c),
-    domain(Vamp::Plugin::TimeDomain),
-    stepSize(_bs ? _bs : 1024),
-    blockSize(_bs ? _bs : 1024),
-    windowType(HanningWindow),
-    startFrame(0),
-    duration(0),
-    sampleRate(0)
-{
-}
-
-PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _ss,
-                                                    size_t _bs, WindowType _wt) :
-    channel(_c),
-    domain(Vamp::Plugin::FrequencyDomain),
-    stepSize(_ss ? _ss : (_bs ? _bs / 2 : 512)),
-    blockSize(_bs ? _bs : 1024),
-    windowType(_wt),
-    startFrame(0),
-    duration(0),
-    sampleRate(0)
-{
-}
-
-PluginTransform::ExecutionContext::ExecutionContext(int _c,
-                                                    const Vamp::PluginBase *_plugin) :
-    channel(_c),
-    domain(Vamp::Plugin::TimeDomain),
-    stepSize(0),
-    blockSize(0),
-    windowType(HanningWindow),
-    startFrame(0),
-    duration(0),
-    sampleRate(0)
-{
-    makeConsistentWithPlugin(_plugin);
-}
-
-bool
-PluginTransform::ExecutionContext::operator==(const ExecutionContext &c)
-{
-    return (c.channel == channel &&
-            c.domain == domain &&
-            c.stepSize == stepSize &&
-            c.blockSize == blockSize &&
-            c.windowType == windowType &&
-            c.startFrame == startFrame &&
-            c.duration == duration &&
-            c.sampleRate == sampleRate);
-}
-
-void
-PluginTransform::ExecutionContext::makeConsistentWithPlugin(const Vamp::PluginBase *_plugin)
-{
-    const Vamp::Plugin *vp = dynamic_cast<const Vamp::Plugin *>(_plugin);
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl;
-        vp = dynamic_cast<const Vamp::PluginHostAdapter *>(_plugin); //!!! why?
-}
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl;
-        vp = dynamic_cast<const Vamp::HostExt::PluginWrapper *>(_plugin); //!!! no, I mean really why?
-    }
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::HostExt::PluginWrapper" << std::endl;
-    }
-
-    if (!vp) {
-        domain = Vamp::Plugin::TimeDomain;
-        if (!stepSize) {
-            if (!blockSize) blockSize = 1024;
-            stepSize = blockSize;
-        } else {
-            if (!blockSize) blockSize = stepSize;
-        }
-    } else {
-        domain = vp->getInputDomain();
-        if (!stepSize) stepSize = vp->getPreferredStepSize();
-        if (!blockSize) blockSize = vp->getPreferredBlockSize();
-        if (!blockSize) blockSize = 1024;
-        if (!stepSize) {
-            if (domain == Vamp::Plugin::FrequencyDomain) {
-//                std::cerr << "frequency domain, step = " << blockSize/2 << std::endl;
-                stepSize = blockSize/2;
-            } else {
-//                std::cerr << "time domain, step = " << blockSize/2 << std::endl;
-                stepSize = blockSize;
-            }
-        }
-    }
-}
-    
-
--- a/plugin/transform/PluginTransform.h	Fri Nov 02 16:50:31 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +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 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 _PLUGIN_TRANSFORM_H_
-#define _PLUGIN_TRANSFORM_H_
-
-#include "Transform.h"
-
-#include "base/Window.h"
-
-#include "vamp-sdk/Plugin.h"
-
-//!!! should this just move back up to Transform? It is after all used
-//directly in all sorts of generic places, like Document
-
-class PluginTransform : public Transform
-{
-public:
-    class ExecutionContext {
-    public:
-        // Time domain:
-        ExecutionContext(int _c = -1, size_t _bs = 0);
-        
-        // Frequency domain:
-        ExecutionContext(int _c, size_t _ss, size_t _bs, WindowType _wt);
-
-        // From plugin defaults:
-        ExecutionContext(int _c, const Vamp::PluginBase *_plugin);
-
-        bool operator==(const ExecutionContext &);
-
-        void makeConsistentWithPlugin(const Vamp::PluginBase *_plugin);
-
-        int channel;
-        Vamp::Plugin::InputDomain domain;
-        size_t stepSize;
-        size_t blockSize;
-        WindowType windowType;
-        size_t startFrame;
-        size_t duration;    // 0 -> whole thing
-        float sampleRate;   // 0 -> model's rate
-    };
-
-protected:
-    PluginTransform(Model *inputModel,
-                    const ExecutionContext &context);
-
-    ExecutionContext m_context;
-};
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/PluginTransformer.cpp	Mon Nov 05 15:31:06 2007 +0000
@@ -0,0 +1,121 @@
+/* -*- 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 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 "PluginTransformer.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+#include "vamp-sdk/hostext/PluginWrapper.h"
+
+PluginTransformer::PluginTransformer(Model *inputModel,
+				 const ExecutionContext &context) :
+    Transformer(inputModel),
+    m_context(context)
+{
+}
+
+PluginTransformer::ExecutionContext::ExecutionContext(int _c, size_t _bs) :
+    channel(_c),
+    domain(Vamp::Plugin::TimeDomain),
+    stepSize(_bs ? _bs : 1024),
+    blockSize(_bs ? _bs : 1024),
+    windowType(HanningWindow),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+}
+
+PluginTransformer::ExecutionContext::ExecutionContext(int _c, size_t _ss,
+                                                    size_t _bs, WindowType _wt) :
+    channel(_c),
+    domain(Vamp::Plugin::FrequencyDomain),
+    stepSize(_ss ? _ss : (_bs ? _bs / 2 : 512)),
+    blockSize(_bs ? _bs : 1024),
+    windowType(_wt),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+}
+
+PluginTransformer::ExecutionContext::ExecutionContext(int _c,
+                                                    const Vamp::PluginBase *_plugin) :
+    channel(_c),
+    domain(Vamp::Plugin::TimeDomain),
+    stepSize(0),
+    blockSize(0),
+    windowType(HanningWindow),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+    makeConsistentWithPlugin(_plugin);
+}
+
+bool
+PluginTransformer::ExecutionContext::operator==(const ExecutionContext &c)
+{
+    return (c.channel == channel &&
+            c.domain == domain &&
+            c.stepSize == stepSize &&
+            c.blockSize == blockSize &&
+            c.windowType == windowType &&
+            c.startFrame == startFrame &&
+            c.duration == duration &&
+            c.sampleRate == sampleRate);
+}
+
+void
+PluginTransformer::ExecutionContext::makeConsistentWithPlugin(const Vamp::PluginBase *_plugin)
+{
+    const Vamp::Plugin *vp = dynamic_cast<const Vamp::Plugin *>(_plugin);
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl;
+        vp = dynamic_cast<const Vamp::PluginHostAdapter *>(_plugin); //!!! why?
+}
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl;
+        vp = dynamic_cast<const Vamp::HostExt::PluginWrapper *>(_plugin); //!!! no, I mean really why?
+    }
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::HostExt::PluginWrapper" << std::endl;
+    }
+
+    if (!vp) {
+        domain = Vamp::Plugin::TimeDomain;
+        if (!stepSize) {
+            if (!blockSize) blockSize = 1024;
+            stepSize = blockSize;
+        } else {
+            if (!blockSize) blockSize = stepSize;
+        }
+    } else {
+        domain = vp->getInputDomain();
+        if (!stepSize) stepSize = vp->getPreferredStepSize();
+        if (!blockSize) blockSize = vp->getPreferredBlockSize();
+        if (!blockSize) blockSize = 1024;
+        if (!stepSize) {
+            if (domain == Vamp::Plugin::FrequencyDomain) {
+//                std::cerr << "frequency domain, step = " << blockSize/2 << std::endl;
+                stepSize = blockSize/2;
+            } else {
+//                std::cerr << "time domain, step = " << blockSize/2 << std::endl;
+                stepSize = blockSize;
+            }
+        }
+    }
+}
+    
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/PluginTransformer.h	Mon Nov 05 15:31:06 2007 +0000
@@ -0,0 +1,63 @@
+/* -*- 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 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 _PLUGIN_TRANSFORMER_H_
+#define _PLUGIN_TRANSFORMER_H_
+
+#include "Transformer.h"
+
+#include "base/Window.h"
+
+#include "vamp-sdk/Plugin.h"
+
+//!!! 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
+{
+public:
+    class ExecutionContext {
+    public:
+        // Time domain:
+        ExecutionContext(int _c = -1, size_t _bs = 0);
+        
+        // Frequency domain:
+        ExecutionContext(int _c, size_t _ss, size_t _bs, WindowType _wt);
+
+        // From plugin defaults:
+        ExecutionContext(int _c, const Vamp::PluginBase *_plugin);
+
+        bool operator==(const ExecutionContext &);
+
+        void makeConsistentWithPlugin(const Vamp::PluginBase *_plugin);
+
+        int channel;
+        Vamp::Plugin::InputDomain domain;
+        size_t stepSize;
+        size_t blockSize;
+        WindowType windowType;
+        size_t startFrame;
+        size_t duration;    // 0 -> whole thing
+        float sampleRate;   // 0 -> model's rate
+    };
+
+protected:
+    PluginTransformer(Model *inputModel,
+                      const ExecutionContext &context);
+
+    ExecutionContext m_context;
+};
+
+#endif
--- a/plugin/transform/RealTimePluginTransform.cpp	Fri Nov 02 16:50:31 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 "RealTimePluginTransform.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>
-
-RealTimePluginTransform::RealTimePluginTransform(Model *inputModel,
-                                                 QString pluginId,
-                                                 const ExecutionContext &context,
-                                                 QString configurationXml,
-                                                 QString units,
-                                                 int output) :
-    PluginTransform(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 << "RealTimePluginTransform::RealTimePluginTransform: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
-
-    RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-	std::cerr << "RealTimePluginTransform: 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 << "RealTimePluginTransform: 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 << "RealTimePluginTransform: 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;
-    }
-}
-
-RealTimePluginTransform::~RealTimePluginTransform()
-{
-    delete m_plugin;
-}
-
-DenseTimeValueModel *
-RealTimePluginTransform::getInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "RealTimePluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-RealTimePluginTransform::run()
-{
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    while (!input->isReady()) {
-        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
-        std::cerr << "RealTimePluginTransform::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/RealTimePluginTransform.h	Fri Nov 02 16:50:31 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_TRANSFORM_H_
-#define _REAL_TIME_PLUGIN_TRANSFORM_H_
-
-#include "PluginTransform.h"
-#include "plugin/RealTimePluginInstance.h"
-
-class DenseTimeValueModel;
-
-class RealTimePluginTransform : public PluginTransform
-{
-public:
-    RealTimePluginTransform(Model *inputModel,
-			    QString plugin,
-                            const ExecutionContext &context,
-			    QString configurationXml = "",
-                            QString units = "",
-			    int output = -1); // -1 -> audio, 0+ -> data
-    virtual ~RealTimePluginTransform();
-
-protected:
-    virtual void run();
-
-    QString m_pluginId;
-    QString m_configurationXml;
-    QString m_units;
-
-    RealTimePluginInstance *m_plugin;
-    int m_outputNo;
-
-    // just casts
-    DenseTimeValueModel *getInput();
-};
-
-#endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimePluginTransformer.cpp	Mon Nov 05 15:31:06 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 "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);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimePluginTransformer.h	Mon Nov 05 15:31:06 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 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/Transform.cpp	Fri Nov 02 16:50:31 2007 +0000
+++ b/plugin/transform/Transform.cpp	Mon Nov 05 15:31:06 2007 +0000
@@ -4,7 +4,7 @@
     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 file copyright 2006-2007 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
@@ -15,18 +15,61 @@
 
 #include "Transform.h"
 
-Transform::Transform(Model *m) :
-    m_input(m),
-    m_output(0),
-    m_detached(false),
-    m_abandoned(false)
+#include "plugin/PluginIdentifier.h"
+
+Transform::Transform() :
+    m_stepSize(0),
+    m_blockSize(0),
+    m_windowType(HanningWindow),
+    m_sampleRate(0)
 {
 }
 
 Transform::~Transform()
 {
-    m_abandoned = true;
-    wait();
-    if (!m_detached) delete m_output;
 }
 
+QString
+Transform::createIdentifier(QString type, QString soName, QString label,
+                            QString output)
+{
+    QString pluginId = PluginIdentifier::createIdentifier(type, soName, label);
+    return pluginId + ":" + output;
+}
+
+void
+Transform::parseIdentifier(QString identifier,
+                           QString &type, QString &soName,
+                           QString &label, QString &output)
+{
+    output = identifier.section(':', 3);
+    PluginIdentifier::parseIdentifier(identifier.section(':', 0, 2),
+                                      type, soName, label);
+}
+
+Transform::Type
+Transform::getType() const
+{
+    QString type, soName, label, output;
+    parseIdentifier(m_id, type, soName, label, output);
+    if (type == "vamp") return FeatureExtraction; //!!! lousy
+    else return RealTimeEffect;
+}
+
+QString
+Transform::getPluginIdentifier() const
+{
+    return m_id.section(':', 0, 2);
+}
+
+QString
+Transform::getOutput() const
+{
+    return m_id.section(':', 3);
+}
+
+void
+Transform::toXml(QTextStream &stream, QString indent, QString extraAttributes) const
+{
+    
+}
--- a/plugin/transform/Transform.h	Fri Nov 02 16:50:31 2007 +0000
+++ b/plugin/transform/Transform.h	Mon Nov 05 15:31:06 2007 +0000
@@ -4,7 +4,7 @@
     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 file copyright 2006-2007 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
@@ -16,46 +16,93 @@
 #ifndef _TRANSFORM_H_
 #define _TRANSFORM_H_
 
-#include "base/Thread.h"
+#include "base/XmlExportable.h"
+#include "base/Window.h"
 
-#include "data/model/Model.h"
+#include <vamp-sdk/RealTime.h>
+
+#include <QString>
 
 typedef QString TransformId;
 
-/**
- * A Transform turns one data model into another.
- *
- * Typically in this application, a Transform might have a
- * DenseTimeValueModel as its input (e.g. an audio waveform) and a
- * SparseOneDimensionalModel (e.g. detected beats) as its output.
- *
- * The Transform typically runs in the background, as a separate
- * thread populating the output model.  The model is available to the
- * user of the Transform immediately, but may be initially empty until
- * the background thread has populated it.
- */
+namespace Vamp {
+    class PluginBase;
+}
 
-class Transform : public Thread
+class Transform : public XmlExportable
 {
 public:
+    Transform();
     virtual ~Transform();
 
-    // 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; }
+    void setIdentifier(TransformId id) { m_id = id; }
+    TransformId getIdentifier() const { return m_id; }
+    
+    void setPlugin(QString pluginIdentifier);
+    void setOutput(QString output);
 
-    Model *getInputModel()  { return m_input; }
-    Model *getOutputModel() { return m_output; }
-    Model *detachOutputModel() { m_detached = true; return m_output; }
+    enum Type { FeatureExtraction, RealTimeEffect };
+
+    Type getType() const;
+    QString getPluginIdentifier() const;
+    QString getOutput() const;
+
+    typedef std::map<QString, float> ParameterMap;
+    
+    ParameterMap getParameters() const { return m_parameters; }
+    void setParameters(const ParameterMap &pm) { m_parameters = pm; }
+
+    typedef std::map<QString, QString> ConfigurationMap;
+
+    ConfigurationMap getConfiguration() const { return m_configuration; }
+    void setConfiguration(const ConfigurationMap &cm) { m_configuration = cm; }
+
+    QString getProgram() const { return m_program; }
+    void setProgram(QString program) { m_program = program; }
+    
+    size_t getStepSize() const { return m_stepSize; }
+    void setStepSize(size_t s) { m_stepSize = s; }
+    
+    size_t getBlockSize() const { return m_blockSize; }
+    void setBlockSize(size_t s) { m_blockSize = s; }
+
+    WindowType getWindowType() const { return m_windowType; }
+    void setWindowType(WindowType type) { m_windowType = type; }
+
+    Vamp::RealTime getStartTime() const { return m_startTime; }
+    void setStartTime(Vamp::RealTime t) { m_startTime = t; }
+
+    Vamp::RealTime getDuration() const { return m_duration; } // 0 -> all
+    void setDuration(Vamp::RealTime d) { m_duration = d; }
+    
+    float getSampleRate() const { return m_sampleRate; } // 0 -> as input
+    void setSampleRate(float rate) { m_sampleRate = rate; }
+
+    void toXml(QTextStream &stream, QString indent = "",
+               QString extraAttributes = "") const;
+
+    static Transform fromXmlString(QString xml);
 
 protected:
-    Transform(Model *m);
+    TransformId m_id; // pluginid:output, that is type:soname:label:output
+    
+    static QString createIdentifier
+    (QString type, QString soName, QString label, QString output);
 
-    Model *m_input; // I don't own this
-    Model *m_output; // I own this, unless...
-    bool m_detached; // ... this is true.
-    bool m_abandoned;
+    static void parseIdentifier
+    (QString identifier,
+     QString &type, QString &soName, QString &label, QString &output);
+
+    ParameterMap m_parameters;
+    ConfigurationMap m_configuration;
+    QString m_program;
+    size_t m_stepSize;
+    size_t m_blockSize;
+    WindowType m_windowType;
+    Vamp::RealTime m_startTime;
+    Vamp::RealTime m_duration;
+    float m_sampleRate;
 };
 
 #endif
+
--- a/plugin/transform/TransformFactory.cpp	Fri Nov 02 16:50:31 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 "TransformFactory.h"
-
-#include "FeatureExtractionPluginTransform.h"
-#include "RealTimePluginTransform.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>
-
-TransformFactory *
-TransformFactory::m_instance = new TransformFactory;
-
-TransformFactory *
-TransformFactory::getInstance()
-{
-    return m_instance;
-}
-
-TransformFactory::~TransformFactory()
-{
-}
-
-TransformFactory::TransformList
-TransformFactory::getAllTransforms()
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<TransformDesc> dset;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
-	dset.insert(i->second);
-    }
-
-    TransformList list;
-    for (std::set<TransformDesc>::const_iterator i = dset.begin();
-	 i != dset.end(); ++i) {
-	list.push_back(*i);
-    }
-
-    return list;
-}
-
-std::vector<QString>
-TransformFactory::getAllTransformTypes()
-{
-    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>
-TransformFactory::getTransformCategories(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>
-TransformFactory::getTransformMakers(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
-TransformFactory::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) {
-
-        TransformDesc 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) {
-
-        TransformDesc 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
-TransformFactory::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: TransformFactory::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: TransformFactory::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] = 
-                TransformDesc(tr("Analysis"),
-                              category,
-                              transformId,
-                              userName,
-                              friendlyName,
-                              description,
-                              maker,
-                              units,
-                              configurable);
-	}
-
-        delete plugin;
-    }
-}
-
-void
-TransformFactory::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: TransformFactory::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: TransformFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl;
-	    continue;
-	}
-	
-//!!!        if (descriptor->controlOutputPortCount == 0 ||
-//            descriptor->audioInputPortCount == 0) continue;
-
-//        std::cout << "TransformFactory::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] = 
-                    TransformDesc(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("Transform 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] =
-                    TransformDesc(type,
-                                  category,
-                                  transformId,
-                                  pluginName,
-                                  pluginName,
-                                  description,
-                                  maker,
-                                  "",
-                                  configurable);
-            }
-        }
-    }
-}
-
-bool
-TransformFactory::haveTransform(TransformId identifier)
-{
-    return (m_transforms.find(identifier) != m_transforms.end());
-}
-
-QString
-TransformFactory::getTransformName(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].name;
-    } else return "";
-}
-
-QString
-TransformFactory::getTransformFriendlyName(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].friendlyName;
-    } else return "";
-}
-
-QString
-TransformFactory::getTransformUnits(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].units;
-    } else return "";
-}
-
-bool
-TransformFactory::isTransformConfigurable(TransformId identifier)
-{
-    if (m_transforms.find(identifier) != m_transforms.end()) {
-	return m_transforms[identifier].configurable;
-    } else return false;
-}
-
-bool
-TransformFactory::getTransformChannelRange(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
-TransformFactory::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 getTransformChannelRange(identifier, minChannels, maxChannels);
-    }
-}
-
-Model *
-TransformFactory::getConfigurationForTransform(TransformId identifier,
-                                               const std::vector<Model *> &candidateInputModels,
-                                               PluginTransform::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 << "getConfigurationForTransform: 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 = PluginTransform::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;
-}
-
-PluginTransform::ExecutionContext
-TransformFactory::getDefaultContextForTransform(TransformId identifier,
-                                                Model *inputModel)
-{
-    PluginTransform::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 = PluginTransform::ExecutionContext(-1, vp);
-            delete vp;
-        }
-    }
-
-    return context;
-}
-
-Transform *
-TransformFactory::createTransform(TransformId identifier, Model *inputModel,
-                                  const PluginTransform::ExecutionContext &context,
-                                  QString configurationXml)
-{
-    Transform *transform = 0;
-
-    QString id = identifier.section(':', 0, 2);
-    QString output = identifier.section(':', 3);
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-        transform = new FeatureExtractionPluginTransform(inputModel,
-                                                         id,
-                                                         context,
-                                                         configurationXml,
-                                                         output);
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-        transform = new RealTimePluginTransform(inputModel,
-                                                id,
-                                                context,
-                                                configurationXml,
-                                                getTransformUnits(identifier),
-                                                output == "A" ? -1 :
-                                                output.toInt());
-    } else {
-        std::cerr << "TransformFactory::createTransform: Unknown transform \""
-                  << identifier.toStdString() << "\"" << std::endl;
-        return transform;
-    }
-
-    if (transform) transform->setObjectName(identifier);
-    return transform;
-}
-
-Model *
-TransformFactory::transform(TransformId identifier, Model *inputModel,
-                            const PluginTransform::ExecutionContext &context,
-                            QString configurationXml)
-{
-    Transform *t = createTransform(identifier, inputModel, context,
-                                   configurationXml);
-
-    if (!t) return 0;
-
-    connect(t, SIGNAL(finished()), this, SLOT(transformFinished()));
-
-    m_runningTransforms.insert(t);
-
-    t->start();
-    Model *model = t->detachOutputModel();
-
-    if (model) {
-        QString imn = inputModel->objectName();
-        QString trn = getTransformFriendlyName(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
-TransformFactory::transformFinished()
-{
-    QObject *s = sender();
-    Transform *transform = dynamic_cast<Transform *>(s);
-    
-    std::cerr << "TransformFactory::transformFinished(" << transform << ")" << std::endl;
-
-    if (!transform) {
-	std::cerr << "WARNING: TransformFactory::transformFinished: sender is not a transform" << std::endl;
-	return;
-    }
-
-    if (m_runningTransforms.find(transform) == m_runningTransforms.end()) {
-        std::cerr << "WARNING: TransformFactory::transformFinished(" 
-                  << transform
-                  << "): I have no record of this transform running!"
-                  << std::endl;
-    }
-
-    m_runningTransforms.erase(transform);
-
-    transform->wait(); // unnecessary but reassuring
-    delete transform;
-}
-
-void
-TransformFactory::modelAboutToBeDeleted(Model *m)
-{
-    TransformSet affected;
-
-    for (TransformSet::iterator i = m_runningTransforms.begin();
-         i != m_runningTransforms.end(); ++i) {
-
-        Transform *t = *i;
-
-        if (t->getInputModel() == m || t->getOutputModel() == m) {
-            affected.insert(t);
-        }
-    }
-
-    for (TransformSet::iterator i = affected.begin();
-         i != affected.end(); ++i) {
-
-        Transform *t = *i;
-
-        t->abandon();
-
-        t->wait(); // this should eventually call back on
-                   // transformFinished, which will remove from
-                   // m_runningTransforms and delete.
-    }
-}
-
--- a/plugin/transform/TransformFactory.h	Fri Nov 02 16:50:31 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +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 _TRANSFORM_FACTORY_H_
-#define _TRANSFORM_FACTORY_H_
-
-#include "Transform.h"
-#include "PluginTransform.h"
-
-#include <map>
-#include <set>
-
-namespace Vamp { class PluginBase; }
-
-class AudioCallbackPlaySource;
-
-class TransformFactory : public QObject
-{
-    Q_OBJECT
-
-public:
-    virtual ~TransformFactory();
-
-    static TransformFactory *getInstance();
-
-    // The identifier is intended to be computer-referenceable, and
-    // unique within the application.  The name is intended to be
-    // human readable.  In principle it doesn't have to be unique, but
-    // the factory will add suffixes to ensure that it is, all the
-    // same (just to avoid user confusion).  The friendly name is a
-    // shorter version of the name.  The type is also intended to be
-    // user-readable, for use in menus.
-
-    struct TransformDesc {
-
-        TransformDesc() { }
-	TransformDesc(QString _type, QString _category,
-                      TransformId _identifier, QString _name,
-                      QString _friendlyName, QString _description,
-                      QString _maker, QString _units, bool _configurable) :
-	    type(_type), category(_category),
-            identifier(_identifier), name(_name),
-            friendlyName(_friendlyName), description(_description),
-            maker(_maker), units(_units), configurable(_configurable) { }
-
-        QString type; // e.g. feature extraction plugin
-        QString category; // e.g. time > onsets
-	TransformId identifier; // e.g. vamp:vamp-aubio:aubioonset
-	QString name; // plugin's name if 1 output, else "name: output"
-        QString friendlyName; // short text for layer name
-        QString description; // sentence describing transform
-        QString maker;
-        QString units;
-        bool configurable;
-
-        bool operator<(const TransformDesc &od) const {
-            return (name < od.name);
-        };
-    };
-    typedef std::vector<TransformDesc> TransformList;
-
-    TransformList getAllTransforms();
-
-    std::vector<QString> getAllTransformTypes();
-
-    std::vector<QString> getTransformCategories(QString transformType);
-    std::vector<QString> getTransformMakers(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 *getConfigurationForTransform(TransformId identifier,
-                                        const std::vector<Model *> &candidateInputModels,
-                                        PluginTransform::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).
-     */
-    PluginTransform::ExecutionContext getDefaultContextForTransform(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 PluginTransform::ExecutionContext &context,
-                     QString configurationXml = "");
-
-    /**
-     * Return true if the given transform is known.
-     */
-    bool haveTransform(TransformId identifier);
-
-    /**
-     * Full name of a transform, suitable for putting on a menu.
-     */
-    QString getTransformName(TransformId identifier);
-
-    /**
-     * Brief but friendly name of a transform, suitable for use
-     * as the name of the output layer.
-     */
-    QString getTransformFriendlyName(TransformId identifier);
-
-    QString getTransformUnits(TransformId identifier);
-
-    /**
-     * Return true if the transform has any configurable parameters,
-     * i.e. if getConfigurationForTransform can ever return a non-trivial
-     * (not equivalent to empty) configuration string.
-     */
-    bool isTransformConfigurable(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 getTransformChannelRange(TransformId identifier,
-                                  int &minChannels, int &maxChannels);
-	
-protected slots:
-    void transformFinished();
-
-    void modelAboutToBeDeleted(Model *);
-
-protected:
-    Transform *createTransform(TransformId identifier, Model *inputModel,
-                               const PluginTransform::ExecutionContext &context,
-                               QString configurationXml);
-
-    struct TransformIdent
-    {
-        TransformId identifier;
-        QString configurationXml;
-    };
-
-    typedef std::map<TransformId, QString> TransformConfigurationMap;
-    TransformConfigurationMap m_lastConfigurations;
-
-    typedef std::map<TransformId, TransformDesc> TransformDescriptionMap;
-    TransformDescriptionMap m_transforms;
-
-    typedef std::set<Transform *> TransformSet;
-    TransformSet m_runningTransforms;
-
-    void populateTransforms();
-    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
-    void populateRealTimePlugins(TransformDescriptionMap &);
-
-    bool getChannelRange(TransformId identifier,
-                         Vamp::PluginBase *plugin, int &min, int &max);
-
-    static TransformFactory *m_instance;
-};
-
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/Transformer.cpp	Mon Nov 05 15:31:06 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 "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;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/Transformer.h	Mon Nov 05 15:31:06 2007 +0000
@@ -0,0 +1,61 @@
+/* -*- 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 _TRANSFORM_H_
+#define _TRANSFORM_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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/TransformerFactory.cpp	Mon Nov 05 15:31:06 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 "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()
+{
+}
+
+TransformerFactory::TransformerList
+TransformerFactory::getAllTransformers()
+{
+    if (m_transforms.empty()) populateTransformers();
+
+    std::set<TransformerDesc> dset;
+    for (TransformerDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+	dset.insert(i->second);
+    }
+
+    TransformerList list;
+    for (std::set<TransformerDesc>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+	list.push_back(*i);
+    }
+
+    return list;
+}
+
+std::vector<QString>
+TransformerFactory::getAllTransformerTypes()
+{
+    if (m_transforms.empty()) populateTransformers();
+
+    std::set<QString> types;
+    for (TransformerDescriptionMap::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()) populateTransformers();
+
+    std::set<QString> categories;
+    for (TransformerDescriptionMap::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()) populateTransformers();
+
+    std::set<QString> makers;
+    for (TransformerDescriptionMap::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::populateTransformers()
+{
+    TransformerDescriptionMap 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 (TransformerDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformerDesc 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 (TransformerDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformerDesc 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(TransformerDescriptionMap &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::populateTransformers: 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::populateTransformers: 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] = 
+                TransformerDesc(tr("Analysis"),
+                              category,
+                              transformId,
+                              userName,
+                              friendlyName,
+                              description,
+                              maker,
+                              units,
+                              configurable);
+	}
+
+        delete plugin;
+    }
+}
+
+void
+TransformerFactory::populateRealTimePlugins(TransformerDescriptionMap &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::populateTransformers: 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::populateTransformers: 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] = 
+                    TransformerDesc(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] =
+                    TransformerDesc(type,
+                                  category,
+                                  transformId,
+                                  pluginName,
+                                  pluginName,
+                                  description,
+                                  maker,
+                                  "",
+                                  configurable);
+            }
+        }
+    }
+}
+
+bool
+TransformerFactory::haveTransformer(TransformerId identifier)
+{
+    return (m_transforms.find(identifier) != m_transforms.end());
+}
+
+QString
+TransformerFactory::getTransformerName(TransformerId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].name;
+    } else return "";
+}
+
+QString
+TransformerFactory::getTransformerFriendlyName(TransformerId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].friendlyName;
+    } else return "";
+}
+
+QString
+TransformerFactory::getTransformerUnits(TransformerId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].units;
+    } else return "";
+}
+
+bool
+TransformerFactory::isTransformerConfigurable(TransformerId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].configurable;
+    } else return false;
+}
+
+bool
+TransformerFactory::getTransformerChannelRange(TransformerId 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(TransformerId 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(TransformerId 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(TransformerId 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(TransformerId 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(TransformerId 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.
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/TransformerFactory.h	Mon Nov 05 15:31:06 2007 +0000
@@ -0,0 +1,190 @@
+/* -*- 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 "Transformer.h"
+#include "PluginTransformer.h"
+
+#include <map>
+#include <set>
+
+namespace Vamp { class PluginBase; }
+
+class AudioCallbackPlaySource;
+
+class TransformerFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~TransformerFactory();
+
+    static TransformerFactory *getInstance();
+
+    // The identifier is intended to be computer-referenceable, and
+    // unique within the application.  The name is intended to be
+    // human readable.  In principle it doesn't have to be unique, but
+    // the factory will add suffixes to ensure that it is, all the
+    // same (just to avoid user confusion).  The friendly name is a
+    // shorter version of the name.  The type is also intended to be
+    // user-readable, for use in menus.
+
+    struct TransformerDesc {
+
+        TransformerDesc() { }
+	TransformerDesc(QString _type, QString _category,
+                      TransformerId _identifier, QString _name,
+                      QString _friendlyName, QString _description,
+                      QString _maker, QString _units, bool _configurable) :
+	    type(_type), category(_category),
+            identifier(_identifier), name(_name),
+            friendlyName(_friendlyName), description(_description),
+            maker(_maker), units(_units), configurable(_configurable) { }
+
+        QString type; // e.g. feature extraction plugin
+        QString category; // e.g. time > onsets
+	TransformerId identifier; // e.g. vamp:vamp-aubio:aubioonset
+	QString name; // plugin's name if 1 output, else "name: output"
+        QString friendlyName; // short text for layer name
+        QString description; // sentence describing transform
+        QString maker;
+        QString units;
+        bool configurable;
+
+        bool operator<(const TransformerDesc &od) const {
+            return (name < od.name);
+        };
+    };
+    typedef std::vector<TransformerDesc> TransformerList;
+
+    TransformerList getAllTransformers();
+
+    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(TransformerId 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(TransformerId 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(TransformerId identifier, Model *inputModel,
+                     const PluginTransformer::ExecutionContext &context,
+                     QString configurationXml = "");
+
+    /**
+     * Return true if the given transform is known.
+     */
+    bool haveTransformer(TransformerId identifier);
+
+    /**
+     * Full name of a transform, suitable for putting on a menu.
+     */
+    QString getTransformerName(TransformerId identifier);
+
+    /**
+     * Brief but friendly name of a transform, suitable for use
+     * as the name of the output layer.
+     */
+    QString getTransformerFriendlyName(TransformerId identifier);
+
+    QString getTransformerUnits(TransformerId 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(TransformerId 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(TransformerId identifier,
+                                  int &minChannels, int &maxChannels);
+	
+protected slots:
+    void transformFinished();
+
+    void modelAboutToBeDeleted(Model *);
+
+protected:
+    Transformer *createTransformer(TransformerId identifier, Model *inputModel,
+                               const PluginTransformer::ExecutionContext &context,
+                               QString configurationXml);
+
+    struct TransformerIdent
+    {
+        TransformerId identifier;
+        QString configurationXml;
+    };
+
+    typedef std::map<TransformerId, QString> TransformerConfigurationMap;
+    TransformerConfigurationMap m_lastConfigurations;
+
+    typedef std::map<TransformerId, TransformerDesc> TransformerDescriptionMap;
+    TransformerDescriptionMap m_transforms;
+
+    typedef std::set<Transformer *> TransformerSet;
+    TransformerSet m_runningTransformers;
+
+    void populateTransformers();
+    void populateFeatureExtractionPlugins(TransformerDescriptionMap &);
+    void populateRealTimePlugins(TransformerDescriptionMap &);
+
+    bool getChannelRange(TransformerId identifier,
+                         Vamp::PluginBase *plugin, int &min, int &max);
+
+    static TransformerFactory *m_instance;
+};
+
+
+#endif