changeset 388:370aa9714ef5

* Move plugin/transform to plain transform. This way transform can depend on model and GUI classes, but plugin doesn't have to.
author Chris Cannam
date Wed, 12 Mar 2008 18:02:17 +0000
parents 7aa1de571880
children a1b6d2e33cab
files plugin/plugin.pro plugin/transform/FeatureExtractionModelTransformer.cpp plugin/transform/FeatureExtractionModelTransformer.h plugin/transform/ModelTransformer.cpp plugin/transform/ModelTransformer.h plugin/transform/ModelTransformerFactory.cpp plugin/transform/ModelTransformerFactory.h plugin/transform/RealTimeEffectModelTransformer.cpp plugin/transform/RealTimeEffectModelTransformer.h plugin/transform/Transform.cpp plugin/transform/Transform.h plugin/transform/TransformDescription.h plugin/transform/TransformFactory.cpp plugin/transform/TransformFactory.h transform/FeatureExtractionModelTransformer.cpp transform/FeatureExtractionModelTransformer.h transform/ModelTransformer.cpp transform/ModelTransformer.h transform/ModelTransformerFactory.cpp transform/ModelTransformerFactory.h transform/RealTimeEffectModelTransformer.cpp transform/RealTimeEffectModelTransformer.h transform/Transform.cpp transform/Transform.h transform/TransformDescription.h transform/TransformFactory.cpp transform/TransformFactory.h
diffstat 27 files changed, 3334 insertions(+), 3345 deletions(-) [+]
line wrap: on
line diff
--- a/plugin/plugin.pro	Wed Mar 12 17:42:56 2008 +0000
+++ b/plugin/plugin.pro	Wed Mar 12 18:02:17 2008 +0000
@@ -5,14 +5,15 @@
 
 CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
 QT += xml
+QT -= gui
 
 TARGET = svplugin
 
 # Doesn't work with this library, which contains C99 as well as C++
 PRECOMPILED_HEADER =
 
-DEPENDPATH += . .. api plugins api/alsa api/alsa/sound transform
-INCLUDEPATH += . .. api api/alsa plugins api/alsa/sound transform
+DEPENDPATH += . .. api plugins api/alsa api/alsa/sound 
+INCLUDEPATH += . .. api api/alsa plugins api/alsa/sound 
 OBJECTS_DIR = tmp_obj
 MOC_DIR = tmp_moc
 
@@ -34,14 +35,7 @@
            api/alsa/seq.h \
            api/alsa/seq_event.h \
            api/alsa/seq_midi_event.h \
-           api/alsa/sound/asequencer.h \
-           transform/FeatureExtractionModelTransformer.h \
-           transform/RealTimeEffectModelTransformer.h \
-           transform/Transform.h \
-           transform/TransformDescription.h \
-           transform/TransformFactory.h \
-           transform/ModelTransformer.h \
-           transform/ModelTransformerFactory.h
+           api/alsa/sound/asequencer.h
 SOURCES += DSSIPluginFactory.cpp \
            DSSIPluginInstance.cpp \
            FeatureExtractionPluginFactory.cpp \
@@ -52,10 +46,5 @@
            RealTimePluginFactory.cpp \
            RealTimePluginInstance.cpp \
            api/dssi_alsa_compat.c \
-           plugins/SamplePlayer.cpp \
-           transform/FeatureExtractionModelTransformer.cpp \
-           transform/RealTimeEffectModelTransformer.cpp \
-           transform/Transform.cpp \
-           transform/TransformFactory.cpp \
-           transform/ModelTransformer.cpp \
-           transform/ModelTransformerFactory.cpp
+           plugins/SamplePlayer.cpp
+
--- a/plugin/transform/FeatureExtractionModelTransformer.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,643 +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 "FeatureExtractionModelTransformer.h"
-
-#include "plugin/FeatureExtractionPluginFactory.h"
-#include "plugin/PluginXml.h"
-#include "vamp-sdk/Plugin.h"
-
-#include "data/model/Model.h"
-#include "base/Window.h"
-#include "base/Exceptions.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 "TransformFactory.h"
-
-#include <iostream>
-
-FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
-                                                                     const Transform &transform) :
-    ModelTransformer(in, transform),
-    m_plugin(0),
-    m_descriptor(0),
-    m_outputFeatureNo(0)
-{
-//    std::cerr << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId.toStdString() << ", outputName " << m_transform.getOutput().toStdString() << std::endl;
-
-    QString pluginId = transform.getPluginIdentifier();
-
-    FeatureExtractionPluginFactory *factory =
-	FeatureExtractionPluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-        m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
-	return;
-    }
-
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) {
-        m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
-        return;
-    }
-
-    m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
-    if (!m_plugin) {
-        m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
-	return;
-    }
-
-    TransformFactory::getInstance()->makeContextConsistentWithPlugin
-        (m_transform, m_plugin);
-
-    TransformFactory::getInstance()->setPluginParameters
-        (m_transform, m_plugin);
-
-    size_t channelCount = input->getChannelCount();
-    if (m_plugin->getMaxChannelCount() < channelCount) {
-	channelCount = 1;
-    }
-    if (m_plugin->getMinChannelCount() > channelCount) {
-        m_message = tr("Cannot provide enough channels to feature extraction plugin \"%1\" (plugin min is %2, max %3; input model has %4)")
-            .arg(pluginId)
-            .arg(m_plugin->getMinChannelCount())
-            .arg(m_plugin->getMaxChannelCount())
-            .arg(input->getChannelCount());
-	return;
-    }
-
-    std::cerr << "Initialising feature extraction plugin with channels = "
-              << channelCount << ", step = " << m_transform.getStepSize()
-              << ", block = " << m_transform.getBlockSize() << std::endl;
-
-    if (!m_plugin->initialise(channelCount,
-                              m_transform.getStepSize(),
-                              m_transform.getBlockSize())) {
-
-        size_t pstep = m_transform.getStepSize();
-        size_t pblock = m_transform.getBlockSize();
-
-        m_transform.setStepSize(0);
-        m_transform.setBlockSize(0);
-        TransformFactory::getInstance()->makeContextConsistentWithPlugin
-            (m_transform, m_plugin);
-
-        if (m_transform.getStepSize() != pstep ||
-            m_transform.getBlockSize() != pblock) {
-            
-            if (!m_plugin->initialise(channelCount,
-                                      m_transform.getStepSize(),
-                                      m_transform.getBlockSize())) {
-
-                m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
-                return;
-
-            } else {
-
-                m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead")
-                    .arg(pluginId)
-                    .arg(pstep)
-                    .arg(pblock)
-                    .arg(m_transform.getStepSize())
-                    .arg(m_transform.getBlockSize());
-            }
-
-        } else {
-
-            m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
-            return;
-        }
-    }
-
-    if (m_transform.getPluginVersion() != "") {
-        QString pv = QString("%1").arg(m_plugin->getPluginVersion());
-        if (pv != m_transform.getPluginVersion()) {
-            QString vm = tr("Transform was configured for version %1 of plugin \"%2\", but the plugin being used is version %3")
-                .arg(m_transform.getPluginVersion())
-                .arg(pluginId)
-                .arg(pv);
-            if (m_message != "") {
-                m_message = QString("%1; %2").arg(vm).arg(m_message);
-            } else {
-                m_message = vm;
-            }
-        }
-    }
-
-    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
-
-    if (outputs.empty()) {
-        m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId);
-	return;
-    }
-    
-    for (size_t i = 0; i < outputs.size(); ++i) {
-	if (m_transform.getOutput() == "" ||
-            outputs[i].identifier == m_transform.getOutput().toStdString()) {
-	    m_outputFeatureNo = i;
-	    m_descriptor = new Vamp::Plugin::OutputDescriptor
-		(outputs[i]);
-	    break;
-	}
-    }
-
-    if (!m_descriptor) {
-        m_message = tr("Plugin \"%1\" has no output named \"%2\"")
-            .arg(pluginId)
-            .arg(m_transform.getOutput());
-	return;
-    }
-
-//    std::cerr << "FeatureExtractionModelTransformer: output sample type "
-//	      << m_descriptor->sampleType << std::endl;
-
-    int binCount = 1;
-    float minValue = 0.0, maxValue = 0.0;
-    bool haveExtents = false;
-    
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-//    std::cerr << "FeatureExtractionModelTransformer: output bin count "
-//	      << binCount << std::endl;
-
-    if (binCount > 0 && m_descriptor->hasKnownExtents) {
-	minValue = m_descriptor->minValue;
-	maxValue = m_descriptor->maxValue;
-        haveExtents = true;
-    }
-
-    size_t modelRate = 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_transform.getStepSize();
-	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;
-    }
-
-    if (m_output) m_output->setSourceModel(input);
-}
-
-FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
-{
-    std::cerr << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << std::endl;
-    delete m_plugin;
-    delete m_descriptor;
-}
-
-DenseTimeValueModel *
-FeatureExtractionModelTransformer::getConformingInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-FeatureExtractionModelTransformer::run()
-{
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
-
-    if (!m_output) return;
-
-    while (!input->isReady()) {
-/*
-        if (dynamic_cast<WaveFileModel *>(input)) {
-            std::cerr << "FeatureExtractionModelTransformer::run: Model is not ready, but it's not a WaveFileModel (it's a " << typeid(input).name() << "), so that's OK" << std::endl;
-            sleep(2);
-            break; // no need to wait
-        }
-*/
-        std::cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << std::endl;
-        sleep(1);
-    }
-
-    size_t sampleRate = 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_transform.getBlockSize() + 2];
-    }
-
-    size_t stepSize = m_transform.getStepSize();
-    size_t blockSize = m_transform.getBlockSize();
-
-    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
-                                  (getConformingInput(),
-                                   channelCount == 1 ? m_input.getChannel() : ch,
-                                   m_transform.getWindowType(),
-                                   blockSize,
-                                   stepSize,
-                                   blockSize,
-                                   false,
-                                   StorageAdviser::PrecisionCritical);
-            if (!model->isOK()) {
-                delete model;
-                setCompletion(100);
-                //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either
-                throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer");
-            }
-            model->resume();
-            fftModels.push_back(model);
-        }
-    }
-
-    long startFrame = m_input.getModel()->getStartFrame();
-    long   endFrame = m_input.getModel()->getEndFrame();
-
-    RealTime contextStartRT = m_transform.getStartTime();
-    RealTime contextDurationRT = m_transform.getDuration();
-
-    long contextStart =
-        RealTime::realTime2Frame(contextStartRT, sampleRate);
-
-    long contextDuration =
-        RealTime::realTime2Frame(contextDurationRT, sampleRate);
-
-    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(blockSize)/2 >
-                contextStart + contextDuration) break;
-        } else {
-            if (blockFrame >= 
-                contextStart + contextDuration) break;
-        }
-
-//	std::cerr << "FeatureExtractionModelTransformer::run: blockFrame "
-//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
-//                  << blockSize << std::endl;
-
-	long completion =
-	    (((blockFrame - contextStart) / stepSize) * 99) /
-	    (contextDuration / stepSize);
-
-	// channelCount is either m_input.getModel()->channelCount or 1
-
-        if (frequencyDomain) {
-            for (size_t ch = 0; ch < channelCount; ++ch) {
-                int column = (blockFrame - startFrame) / stepSize;
-                for (size_t i = 0; i <= blockSize/2; ++i) {
-                    fftModels[ch]->getValuesAt
-                        (column, i, buffers[ch][i*2], buffers[ch][i*2+1]);
-                }
-            }
-        } else {
-            getFrames(channelCount, blockFrame, blockSize, buffers);
-        }
-
-	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 += stepSize;
-    }
-
-    if (m_abandoned) return;
-
-    Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
-
-    for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
-	Vamp::Plugin::Feature feature =
-	    features[m_outputFeatureNo][fi];
-	addFeature(blockFrame, feature);
-    }
-
-    if (frequencyDomain) {
-        for (size_t ch = 0; ch < channelCount; ++ch) {
-            delete fftModels[ch];
-        }
-    }
-
-    setCompletion(100);
-}
-
-void
-FeatureExtractionModelTransformer::getFrames(int channelCount,
-                                             long startFrame, long size,
-                                             float **buffers)
-{
-    long offset = 0;
-
-    if (startFrame < 0) {
-        for (int c = 0; c < channelCount; ++c) {
-            for (int i = 0; i < size && startFrame + i < 0; ++i) {
-                buffers[c][i] = 0.0f;
-            }
-        }
-        offset = -startFrame;
-        size -= offset;
-        if (size <= 0) return;
-        startFrame = 0;
-    }
-
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
-    
-    long got = 0;
-
-    if (channelCount == 1) {
-
-        got = input->getData(m_input.getChannel(), startFrame, size,
-                             buffers[0] + offset);
-
-        if (m_input.getChannel() == -1 && input->getChannelCount() > 1) {
-            // use mean instead of sum, as plugin input
-            float cc = float(input->getChannelCount());
-            for (long i = 0; i < size; ++i) {
-                buffers[0][i + offset] /= cc;
-            }
-        }
-
-    } else {
-
-        float **writebuf = buffers;
-        if (offset > 0) {
-            writebuf = new float *[channelCount];
-            for (int i = 0; i < channelCount; ++i) {
-                writebuf[i] = buffers[i] + offset;
-            }
-        }
-
-        got = input->getData(0, channelCount-1, startFrame, size, writebuf);
-
-        if (writebuf != buffers) delete[] writebuf;
-    }
-
-    while (got < size) {
-        for (int c = 0; c < channelCount; ++c) {
-            buffers[c][got + offset] = 0.0;
-        }
-        ++got;
-    }
-}
-
-void
-FeatureExtractionModelTransformer::addFeature(size_t blockFrame,
-					     const Vamp::Plugin::Feature &feature)
-{
-    size_t inputRate = m_input.getModel()->getSampleRate();
-
-//    std::cerr << "FeatureExtractionModelTransformer::addFeature("
-//	      << blockFrame << ")" << std::endl;
-
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-    size_t frame = blockFrame;
-
-    if (m_descriptor->sampleType ==
-	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-	if (!feature.hasTimestamp) {
-	    std::cerr
-		<< "WARNING: FeatureExtractionModelTransformer::addFeature: "
-		<< "Feature has variable sample rate but no timestamp!"
-		<< std::endl;
-	    return;
-	} else {
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
-	}
-
-    } else if (m_descriptor->sampleType ==
-	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
-
-	if (feature.hasTimestamp) {
-	    //!!! warning: sampleRate may be non-integral
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp,
-                                                   lrintf(m_descriptor->sampleRate));
-	} else {
-	    frame = m_output->getEndFrame();
-	}
-    }
-	
-    if (binCount == 0) {
-
-	SparseOneDimensionalModel *model =
-            getConformingOutput<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 =
-            getConformingOutput<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];
-        if (velocity < 0) velocity = 127;
-        if (velocity > 127) velocity = 127;
-
-        NoteModel *model = getConformingOutput<NoteModel>();
-        if (!model) return;
-
-        model->addPoint(NoteModel::Point(frame, pitch,
-                                         lrintf(duration),
-                                         velocity / 127.f,
-                                         feature.label.c_str()));
-	
-    } else {
-	
-	DenseThreeDimensionalModel::Column values = feature.values;
-	
-	EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>();
-	if (!model) return;
-
-	model->setColumn(frame / model->getResolution(), values);
-    }
-}
-
-void
-FeatureExtractionModelTransformer::setCompletion(int completion)
-{
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
-//    std::cerr << "FeatureExtractionModelTransformer::setCompletion("
-//              << completion << ")" << std::endl;
-
-    if (binCount == 0) {
-
-	SparseOneDimensionalModel *model =
-            getConformingOutput<SparseOneDimensionalModel>();
-	if (!model) return;
-	model->setCompletion(completion, true); //!!!m_context.updates);
-
-    } else if (binCount == 1) {
-
-	SparseTimeValueModel *model =
-            getConformingOutput<SparseTimeValueModel>();
-	if (!model) return;
-	model->setCompletion(completion, true); //!!!m_context.updates);
-
-    } else if (m_descriptor->sampleType ==
-	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
-
-	NoteModel *model =
-            getConformingOutput<NoteModel>();
-	if (!model) return;
-	model->setCompletion(completion, true); //!!!m_context.updates);
-
-    } else {
-
-	EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>();
-	if (!model) return;
-	model->setCompletion(completion, true); //!!!m_context.updates);
-    }
-}
-
--- a/plugin/transform/FeatureExtractionModelTransformer.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
-#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
-
-#include "ModelTransformer.h"
-
-#include <QString>
-
-#include <vamp-sdk/Plugin.h>
-
-#include <iostream>
-
-class DenseTimeValueModel;
-
-class FeatureExtractionModelTransformer : public ModelTransformer
-{
-    Q_OBJECT
-
-public:
-    FeatureExtractionModelTransformer(Input input,
-                                      const Transform &transform);
-    virtual ~FeatureExtractionModelTransformer();
-
-protected:
-    virtual void run();
-
-    Vamp::Plugin *m_plugin;
-    Vamp::Plugin::OutputDescriptor *m_descriptor;
-    int m_outputFeatureNo;
-
-    void addFeature(size_t blockFrame,
-		    const Vamp::Plugin::Feature &feature);
-
-    void setCompletion(int);
-
-    void getFrames(int channelCount, long startFrame, long size,
-                   float **buffer);
-
-    // just casts
-    DenseTimeValueModel *getConformingInput();
-    template <typename ModelClass> ModelClass *getConformingOutput() {
-	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
-	if (!mc) {
-	    std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl;
-	}
-	return mc;
-    }
-};
-
-#endif
-
--- a/plugin/transform/ModelTransformer.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-   
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "ModelTransformer.h"
-
-ModelTransformer::ModelTransformer(Input input, const Transform &transform) :
-    m_transform(transform),
-    m_input(input),
-    m_output(0),
-    m_detached(false),
-    m_abandoned(false)
-{
-}
-
-ModelTransformer::~ModelTransformer()
-{
-    m_abandoned = true;
-    wait();
-    if (!m_detached) delete m_output;
-}
-
--- a/plugin/transform/ModelTransformer.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-   
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _TRANSFORMER_H_
-#define _TRANSFORMER_H_
-
-#include "base/Thread.h"
-
-#include "data/model/Model.h"
-
-#include "Transform.h"
-
-/**
- * A ModelTransformer turns one data model into another.
- *
- * Typically in this application, a ModelTransformer might have a
- * DenseTimeValueModel as its input (e.g. an audio waveform) and a
- * SparseOneDimensionalModel (e.g. detected beats) as its output.
- *
- * The ModelTransformer typically runs in the background, as a
- * separate thread populating the output model.  The model is
- * available to the user of the ModelTransformer immediately, but may
- * be initially empty until the background thread has populated it.
- */
-
-class ModelTransformer : public Thread
-{
-public:
-    virtual ~ModelTransformer();
-
-    class Input {
-    public:
-        Input(Model *m) : m_model(m), m_channel(-1) { }
-        Input(Model *m, int c) : m_model(m), m_channel(c) { }
-
-        Model *getModel() const { return m_model; }
-        void setModel(Model *m) { m_model = m; }
-
-        int getChannel() const { return m_channel; }
-        void setChannel(int c) { m_channel = c; }
-
-    protected:
-        Model *m_model;
-        int m_channel;
-    };
-
-    /**
-     * Hint to the processing thread that it should give up, for
-     * example because the process is going to exit or we want to get
-     * rid of the input model.  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; }
-
-    /**
-     * Return the input model for the transform.
-     */
-    Model *getInputModel()  { return m_input.getModel(); }
-
-    /**
-     * Return the input channel spec for the transform.
-     */
-    int getInputChannel() { return m_input.getChannel(); }
-
-    /**
-     * Return the output model created by the transform.  Returns a
-     * null model if the transform could not be initialised; an error
-     * message may be available via getMessage() in this situation.
-     */
-    Model *getOutputModel() { return m_output; }
-
-    /**
-     * Return the output model, also detaching it from the transformer
-     * so that it will not be deleted when the transformer is.  The
-     * caller takes ownership of the model.
-     */
-    Model *detachOutputModel() { m_detached = true; return m_output; }
-
-    /**
-     * Return a warning or error message.  If getOutputModel returned
-     * a null pointer, this should contain a fatal error message for
-     * the transformer; otherwise it may contain a warning to show to
-     * the user about e.g. suboptimal block size or whatever.  
-     */
-    QString getMessage() const { return m_message; }
-
-protected:
-    ModelTransformer(Input input, const Transform &transform);
-
-    Transform m_transform;
-    Input m_input; // I don't own the model in this
-    Model *m_output; // I own this, unless...
-    bool m_detached; // ... this is true.
-    bool m_abandoned;
-    QString m_message;
-};
-
-#endif
--- a/plugin/transform/ModelTransformerFactory.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,450 +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 "ModelTransformerFactory.h"
-
-#include "FeatureExtractionModelTransformer.h"
-#include "RealTimeEffectModelTransformer.h"
-
-#include "TransformFactory.h"
-
-#include "plugin/FeatureExtractionPluginFactory.h"
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/PluginXml.h"
-
-#include "widgets/PluginParameterDialog.h"
-
-#include "data/model/DenseTimeValueModel.h"
-
-#include "vamp-sdk/PluginHostAdapter.h"
-
-#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
-
-#include <iostream>
-#include <set>
-
-#include <QRegExp>
-
-ModelTransformerFactory *
-ModelTransformerFactory::m_instance = new ModelTransformerFactory;
-
-ModelTransformerFactory *
-ModelTransformerFactory::getInstance()
-{
-    return m_instance;
-}
-
-ModelTransformerFactory::~ModelTransformerFactory()
-{
-}
-
-bool
-ModelTransformerFactory::getChannelRange(TransformId identifier,
-                                         Vamp::PluginBase *plugin,
-                                         int &minChannels, int &maxChannels)
-{
-    Vamp::Plugin *vp = 0;
-    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
-        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
-        minChannels = vp->getMinChannelCount();
-        maxChannels = vp->getMaxChannelCount();
-        return true;
-    } else {
-        return TransformFactory::getInstance()->
-            getTransformChannelRange(identifier, minChannels, maxChannels);
-    }
-}
-
-ModelTransformer::Input
-ModelTransformerFactory::getConfigurationForTransform(Transform &transform,
-                                                      const std::vector<Model *> &candidateInputModels,
-                                                      Model *defaultInputModel,
-                                                      AudioCallbackPlaySource *source,
-                                                      size_t startFrame,
-                                                      size_t duration)
-{
-    ModelTransformer::Input input(0);
-
-    if (candidateInputModels.empty()) return input;
-
-    //!!! 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];
-    QStringList candidateModelNames;
-    QString defaultModelName;
-    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);
-        if (candidateInputModels[i] == defaultInputModel) {
-            defaultModelName = modelName;
-        }
-    }
-
-    QString id = transform.getPluginIdentifier();
-    QString output = transform.getOutput();
-    QString outputLabel = "";
-    QString outputDescription = "";
-    
-    bool ok = false;
-    QString configurationXml = m_lastConfigurations[transform.getIdentifier()];
-
-    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) {
-
-        // Ensure block size etc are valid
-        TransformFactory::getInstance()->
-            makeContextConsistentWithPlugin(transform, plugin);
-
-        // Prepare the plugin with any existing parameters already
-        // found in the transform
-        TransformFactory::getInstance()->
-            setPluginParameters(transform, plugin);
-        
-        // For this interactive usage, we want to override those with
-        // whatever the user chose last time around
-        PluginXml(plugin).setParametersFromXml(configurationXml);
-
-        int sourceChannels = 1;
-        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
-            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
-                ->getChannelCount();
-        }
-
-        int minChannels = 1, maxChannels = sourceChannels;
-        getChannelRange(transform.getIdentifier(), plugin,
-                        minChannels, maxChannels);
-
-        int targetChannels = sourceChannels;
-        if (!effect) {
-            if (sourceChannels < minChannels) targetChannels = minChannels;
-            if (sourceChannels > maxChannels) targetChannels = maxChannels;
-        }
-
-        int defaultChannel = -1; //!!! no longer saved! [was context.channel]
-
-        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
-
-        if (candidateModelNames.size() > 1 && !generator) {
-            dialog->setCandidateInputModels(candidateModelNames,
-                                            defaultModelName);
-        }
-
-        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;
-        }
-        
-        // Write parameters back to transform object
-        TransformFactory::getInstance()->
-            setParametersFromPlugin(transform, plugin);
-
-        input.setChannel(dialog->getChannel());
-        
-        //!!! The dialog ought to be taking & returning transform
-        //objects and input objects and stuff rather than passing
-        //around all this misc stuff, but that's for tomorrow
-        //(whenever that may be)
-
-        if (startFrame != 0 || duration != 0) {
-            if (dialog->getSelectionOnly()) {
-                transform.setStartTime(RealTime::frame2RealTime
-                                       (startFrame, inputModel->getSampleRate()));
-                transform.setDuration(RealTime::frame2RealTime
-                                      (duration, inputModel->getSampleRate()));
-            }
-        }
-
-        size_t stepSize = 0, blockSize = 0;
-        WindowType windowType = HanningWindow;
-
-        dialog->getProcessingParameters(stepSize,
-                                        blockSize,
-                                        windowType);
-
-        transform.setStepSize(stepSize);
-        transform.setBlockSize(blockSize);
-        transform.setWindowType(windowType);
-
-        TransformFactory::getInstance()->
-            makeContextConsistentWithPlugin(transform, plugin);
-
-        configurationXml = PluginXml(plugin).toXmlString();
-
-        delete dialog;
-
-        if (effect && source) {
-            source->setAuditioningPlugin(0); // will delete our plugin
-        } else {
-            delete plugin;
-        }
-    }
-
-    if (ok) {
-        m_lastConfigurations[transform.getIdentifier()] = configurationXml;
-        input.setModel(inputModel);
-    }
-
-    return input;
-}
-/*!!!
-PluginTransformer::ExecutionContext
-ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
-                                                Model *inputModel)
-{
-    PluginTransformer::ExecutionContext context(-1);
-
-    QString id = identifier.section(':', 0, 2);
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        Vamp::Plugin *vp =
-            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, inputModel ? inputModel->getSampleRate() : 48000);
-
-        if (vp) {
-            context = PluginTransformer::ExecutionContext(-1, vp);
-            delete vp;
-        }
-    }
-
-    return context;
-}
-*/
-ModelTransformer *
-ModelTransformerFactory::createTransformer(const Transform &transform,
-                                           const ModelTransformer::Input &input)
-{
-    ModelTransformer *transformer = 0;
-
-    QString id = transform.getPluginIdentifier();
-
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        transformer =
-            new FeatureExtractionModelTransformer(input, transform);
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-
-        transformer =
-            new RealTimeEffectModelTransformer(input, transform);
-
-    } else {
-        std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
-                  << transform.getIdentifier().toStdString() << "\"" << std::endl;
-        return transformer;
-    }
-
-    if (transformer) transformer->setObjectName(transform.getIdentifier());
-    return transformer;
-}
-
-Model *
-ModelTransformerFactory::transform(const Transform &transform,
-                                   const ModelTransformer::Input &input,
-                                   QString &message)
-{
-    ModelTransformer *t = createTransformer(transform, input);
-    if (!t) return 0;
-
-    connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
-
-    m_runningTransformers.insert(t);
-
-    t->start();
-    Model *model = t->detachOutputModel();
-
-    if (model) {
-        QString imn = input.getModel()->objectName();
-        QString trn =
-            TransformFactory::getInstance()->getTransformFriendlyName
-            (transform.getIdentifier());
-        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();
-    }
-
-    message = t->getMessage();
-
-    return model;
-}
-
-void
-ModelTransformerFactory::transformerFinished()
-{
-    QObject *s = sender();
-    ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
-    
-    std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
-
-    if (!transformer) {
-	std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
-	return;
-    }
-
-    if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
-        std::cerr << "WARNING: ModelTransformerFactory::transformerFinished(" 
-                  << transformer
-                  << "): I have no record of this transformer running!"
-                  << std::endl;
-    }
-
-    m_runningTransformers.erase(transformer);
-
-    transformer->wait(); // unnecessary but reassuring
-    delete transformer;
-}
-
-void
-ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
-{
-    TransformerSet affected;
-
-    for (TransformerSet::iterator i = m_runningTransformers.begin();
-         i != m_runningTransformers.end(); ++i) {
-
-        ModelTransformer *t = *i;
-
-        if (t->getInputModel() == m || t->getOutputModel() == m) {
-            affected.insert(t);
-        }
-    }
-
-    for (TransformerSet::iterator i = affected.begin();
-         i != affected.end(); ++i) {
-
-        ModelTransformer *t = *i;
-
-        t->abandon();
-
-        t->wait(); // this should eventually call back on
-                   // transformerFinished, which will remove from
-                   // m_runningTransformers and delete.
-    }
-}
-
--- a/plugin/transform/ModelTransformerFactory.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +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 _MODEL_TRANSFORMER_FACTORY_H_
-#define _MODEL_TRANSFORMER_FACTORY_H_
-
-#include "Transform.h"
-#include "TransformDescription.h"
-
-#include "ModelTransformer.h"
-
-#include <map>
-#include <set>
-
-namespace Vamp { class PluginBase; }
-
-class AudioCallbackPlaySource;
-
-class ModelTransformerFactory : public QObject
-{
-    Q_OBJECT
-
-public:
-    virtual ~ModelTransformerFactory();
-
-    static ModelTransformerFactory *getInstance();
-
-    /**
-     * Fill out the configuration for the given transform (by asking
-     * the user, most likely).  Returns the selected input model and
-     * channel if the transform is acceptable, or an input with a null
-     * model if the operation should be cancelled.  Audio callback
-     * play source may be used to audition effects plugins, if
-     * provided.
-     */
-    ModelTransformer::Input
-    getConfigurationForTransform(Transform &transform,
-                                 const std::vector<Model *> &candidateInputModels,
-                                 Model *defaultInputModel,
-                                 AudioCallbackPlaySource *source = 0,
-                                 size_t startFrame = 0,
-                                 size_t duration = 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.  Set message if there is any error or
-     * warning to report.
-     * 
-     * The returned model is owned by the caller and must be deleted
-     * when no longer needed.
-     */
-    Model *transform(const Transform &transform,
-                     const ModelTransformer::Input &input,
-                     QString &message);
-
-protected slots:
-    void transformerFinished();
-
-    void modelAboutToBeDeleted(Model *);
-
-protected:
-    ModelTransformer *createTransformer(const Transform &transform,
-                                        const ModelTransformer::Input &input);
-
-    typedef std::map<TransformId, QString> TransformerConfigurationMap;
-    TransformerConfigurationMap m_lastConfigurations;
-
-    typedef std::set<ModelTransformer *> TransformerSet;
-    TransformerSet m_runningTransformers;
-
-    bool getChannelRange(TransformId identifier,
-                         Vamp::PluginBase *plugin, int &min, int &max);
-
-    static ModelTransformerFactory *m_instance;
-};
-
-
-#endif
--- a/plugin/transform/RealTimeEffectModelTransformer.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +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 "RealTimeEffectModelTransformer.h"
-
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/RealTimePluginInstance.h"
-#include "plugin/PluginXml.h"
-
-#include "data/model/Model.h"
-#include "data/model/SparseTimeValueModel.h"
-#include "data/model/DenseTimeValueModel.h"
-#include "data/model/WritableWaveFileModel.h"
-#include "data/model/WaveFileModel.h"
-
-#include "TransformFactory.h"
-
-#include <iostream>
-
-RealTimeEffectModelTransformer::RealTimeEffectModelTransformer(Input in,
-                                                               const Transform &transform) :
-    ModelTransformer(in, transform),
-    m_plugin(0)
-{
-    m_units = TransformFactory::getInstance()->getTransformUnits
-        (transform.getIdentifier());
-    m_outputNo =
-        (transform.getOutput() == "A") ? -1 : transform.getOutput().toInt();
-
-    QString pluginId = transform.getPluginIdentifier();
-
-    if (!m_transform.getBlockSize()) m_transform.setBlockSize(1024);
-
-//    std::cerr << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
-
-    RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
-
-    if (!factory) {
-	std::cerr << "RealTimeEffectModelTransformer: No factory available for plugin id \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
-
-    m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
-                                          input->getSampleRate(),
-                                          m_transform.getBlockSize(),
-                                          input->getChannelCount());
-
-    if (!m_plugin) {
-	std::cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
-		  << pluginId.toStdString() << "\"" << std::endl;
-	return;
-    }
-
-    TransformFactory::getInstance()->setPluginParameters(m_transform, m_plugin);
-
-    if (m_outputNo >= 0 &&
-        m_outputNo >= int(m_plugin->getControlOutputCount())) {
-        std::cerr << "RealTimeEffectModelTransformer: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
-        return;
-    }
-
-    if (m_outputNo == -1) {
-
-        size_t outputChannels = m_plugin->getAudioOutputCount();
-        if (outputChannels > input->getChannelCount()) {
-            outputChannels = input->getChannelCount();
-        }
-
-        WritableWaveFileModel *model = new WritableWaveFileModel
-            (input->getSampleRate(), outputChannels);
-
-        m_output = model;
-
-    } else {
-	
-        SparseTimeValueModel *model = new SparseTimeValueModel
-            (input->getSampleRate(), m_transform.getBlockSize(), 0.0, 0.0, false);
-
-        if (m_units != "") model->setScaleUnits(m_units);
-
-        m_output = model;
-    }
-}
-
-RealTimeEffectModelTransformer::~RealTimeEffectModelTransformer()
-{
-    delete m_plugin;
-}
-
-DenseTimeValueModel *
-RealTimeEffectModelTransformer::getConformingInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "RealTimeEffectModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-void
-RealTimeEffectModelTransformer::run()
-{
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
-
-    while (!input->isReady()) {
-        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
-        std::cerr << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << std::endl;
-        sleep(1);
-    }
-
-    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
-    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
-    if (!stvm && !wwfm) return;
-
-    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
-
-    size_t sampleRate = input->getSampleRate();
-    size_t channelCount = input->getChannelCount();
-    if (!wwfm && m_input.getChannel() != -1) channelCount = 1;
-
-    long blockSize = m_plugin->getBufferSize();
-
-    float **inbufs = m_plugin->getAudioInputBuffers();
-
-    long startFrame = m_input.getModel()->getStartFrame();
-    long   endFrame = m_input.getModel()->getEndFrame();
-    
-    RealTime contextStartRT = m_transform.getStartTime();
-    RealTime contextDurationRT = m_transform.getDuration();
-
-    long contextStart =
-        RealTime::realTime2Frame(contextStartRT, sampleRate);
-
-    long contextDuration =
-        RealTime::realTime2Frame(contextDurationRT, sampleRate);
-
-    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_input.getChannel(), 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 {
-            got = input->getData(0, channelCount - 1,
-                                 blockFrame, blockSize,
-                                 inbufs);
-            while (got < blockSize) {
-                for (size_t ch = 0; ch < channelCount; ++ch) {
-                    inbufs[ch][got] = 0.0;
-                }
-                ++got;
-	    }
-            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/RealTimeEffectModelTransformer.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _REAL_TIME_PLUGIN_TRANSFORMER_H_
-#define _REAL_TIME_PLUGIN_TRANSFORMER_H_
-
-#include "ModelTransformer.h"
-#include "plugin/RealTimePluginInstance.h"
-
-class DenseTimeValueModel;
-
-class RealTimeEffectModelTransformer : public ModelTransformer
-{
-public:
-    RealTimeEffectModelTransformer(Input input,
-                                   const Transform &transform);
-    virtual ~RealTimeEffectModelTransformer();
-
-protected:
-    virtual void run();
-
-    QString m_units;
-    RealTimePluginInstance *m_plugin;
-    int m_outputNo;
-
-    // just casts
-    DenseTimeValueModel *getConformingInput();
-};
-
-#endif
-
--- a/plugin/transform/Transform.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +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-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
-    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 "Transform.h"
-
-#include "plugin/PluginIdentifier.h"
-
-#include "plugin/FeatureExtractionPluginFactory.h"
-
-#include <QXmlAttributes>
-
-#include <QDomDocument>
-#include <QDomElement>
-#include <QDomNamedNodeMap>
-#include <QDomAttr>
-
-#include <QTextStream>
-
-#include <iostream>
-
-Transform::Transform() :
-    m_stepSize(0),
-    m_blockSize(0),
-    m_windowType(HanningWindow),
-    m_sampleRate(0)
-{
-}
-
-Transform::Transform(QString xml) :
-    m_stepSize(0),
-    m_blockSize(0),
-    m_windowType(HanningWindow),
-    m_sampleRate(0)
-{
-    QDomDocument doc;
-    
-    QString error;
-    int errorLine;
-    int errorColumn;
-
-    if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
-        std::cerr << "Transform::Transform: Error in parsing XML: "
-                  << error.toStdString() << " at line " << errorLine
-                  << ", column " << errorColumn << std::endl;
-        std::cerr << "Input follows:" << std::endl;
-        std::cerr << xml.toStdString() << std::endl;
-        std::cerr << "Input ends." << std::endl;
-        return;
-    }
-    
-    QDomElement transformElt = doc.firstChildElement("transform");
-    QDomNamedNodeMap attrNodes = transformElt.attributes();
-    QXmlAttributes attrs;
-
-    for (unsigned int i = 0; i < attrNodes.length(); ++i) {
-        QDomAttr attr = attrNodes.item(i).toAttr();
-        if (!attr.isNull()) attrs.append(attr.name(), "", "", attr.value());
-    }
-
-    setFromXmlAttributes(attrs);
-
-    for (QDomElement paramElt = transformElt.firstChildElement("parameter");
-         !paramElt.isNull();
-         paramElt = paramElt.nextSiblingElement("parameter")) {
-
-        QDomNamedNodeMap paramAttrs = paramElt.attributes();
-
-        QDomAttr nameAttr = paramAttrs.namedItem("name").toAttr();
-        if (nameAttr.isNull() || nameAttr.value() == "") continue;
-        
-        QDomAttr valueAttr = paramAttrs.namedItem("value").toAttr();
-        if (valueAttr.isNull() || valueAttr.value() == "") continue;
-
-        setParameter(nameAttr.value(), valueAttr.value().toFloat());
-    }
-
-    for (QDomElement configElt = transformElt.firstChildElement("configuration");
-         !configElt.isNull();
-         configElt = configElt.nextSiblingElement("configuration")) {
-
-        QDomNamedNodeMap configAttrs = configElt.attributes();
-
-        QDomAttr nameAttr = configAttrs.namedItem("name").toAttr();
-        if (nameAttr.isNull() || nameAttr.value() == "") continue;
-        
-        QDomAttr valueAttr = configAttrs.namedItem("value").toAttr();
-        if (valueAttr.isNull() || valueAttr.value() == "") continue;
-
-        setConfigurationValue(nameAttr.value(), valueAttr.value());
-    }
-}
-
-Transform::~Transform()
-{
-}
-
-bool
-Transform::operator==(const Transform &t)
-{
-    return 
-        m_id == t.m_id &&
-        m_parameters == t.m_parameters &&
-        m_configuration == t.m_configuration &&
-        m_program == t.m_program &&
-        m_stepSize == t.m_stepSize &&
-        m_blockSize == t.m_blockSize &&
-        m_windowType == t.m_windowType &&
-        m_startTime == t.m_startTime &&
-        m_duration == t.m_duration &&
-        m_sampleRate == t.m_sampleRate;
-}
-
-void
-Transform::setIdentifier(TransformId id)
-{
-    m_id = id;
-}
-
-TransformId
-Transform::getIdentifier() const
-{
-    return m_id;
-}
-
-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
-{
-    if (FeatureExtractionPluginFactory::instanceFor(getPluginIdentifier())) {
-        return FeatureExtraction;
-    } else {
-        // We don't have an unknown/invalid return value, so always
-        // return this
-        return RealTimeEffect;
-    }
-}
-
-QString
-Transform::getPluginIdentifier() const
-{
-    return m_id.section(':', 0, 2);
-}
-
-QString
-Transform::getOutput() const
-{
-    return m_id.section(':', 3);
-}
-
-void
-Transform::setPluginIdentifier(QString pluginIdentifier)
-{
-    m_id = pluginIdentifier + ':' + getOutput();
-}
-
-void
-Transform::setOutput(QString output)
-{
-    m_id = getPluginIdentifier() + ':' + output;
-}
-
-TransformId
-Transform::getIdentifierForPluginOutput(QString pluginIdentifier,
-                                        QString output)
-{
-    return pluginIdentifier + ':' + output;
-}
-
-const Transform::ParameterMap &
-Transform::getParameters() const
-{
-    return m_parameters;
-}
-
-void
-Transform::setParameters(const ParameterMap &pm)
-{
-    m_parameters = pm;
-}
-
-void
-Transform::setParameter(QString name, float value)
-{
-    std::cerr << "Transform::setParameter(" << name.toStdString()
-              << ") -> " << value << std::endl;
-    m_parameters[name] = value;
-}
-
-const Transform::ConfigurationMap &
-Transform::getConfiguration() const
-{
-    return m_configuration;
-}
-
-void
-Transform::setConfiguration(const ConfigurationMap &cm)
-{
-    m_configuration = cm;
-}
-
-void
-Transform::setConfigurationValue(QString name, QString value)
-{
-    std::cerr << "Transform::setConfigurationValue(" << name.toStdString()
-              << ") -> " << value.toStdString() << std::endl;
-    m_configuration[name] = value;
-}
-
-QString
-Transform::getPluginVersion() const
-{
-    return m_pluginVersion;
-}
-
-void
-Transform::setPluginVersion(QString version)
-{
-    m_pluginVersion = version;
-}
-
-QString
-Transform::getProgram() const
-{
-    return m_program;
-}
-
-void
-Transform::setProgram(QString program)
-{
-    m_program = program;
-}
-
-    
-size_t
-Transform::getStepSize() const
-{
-    return m_stepSize;
-}
-
-void
-Transform::setStepSize(size_t s)
-{
-    m_stepSize = s;
-}
-    
-size_t
-Transform::getBlockSize() const
-{
-    return m_blockSize;
-}
-
-void
-Transform::setBlockSize(size_t s)
-{
-    m_blockSize = s;
-}
-
-WindowType
-Transform::getWindowType() const
-{
-    return m_windowType;
-}
-
-void
-Transform::setWindowType(WindowType type)
-{
-    m_windowType = type;
-}
-
-RealTime
-Transform::getStartTime() const
-{
-    return m_startTime;
-}
-
-void
-Transform::setStartTime(RealTime t)
-{
-    m_startTime = t;
-}
-
-RealTime
-Transform::getDuration() const
-{
-    return m_duration;
-}
-
-void
-Transform::setDuration(RealTime d)
-{
-    m_duration = d;
-}
-    
-float
-Transform::getSampleRate() const
-{
-    return m_sampleRate;
-}
-
-void
-Transform::setSampleRate(float rate)
-{
-    m_sampleRate = rate;
-}
-
-void
-Transform::toXml(QTextStream &out, QString indent, QString extraAttributes) const
-{
-    out << indent;
-
-    bool haveContent = true;
-    if (m_parameters.empty() && m_configuration.empty()) haveContent = false;
-
-    out << QString("<transform id=\"%1\" pluginVersion=\"%2\" program=\"%3\" stepSize=\"%4\" blockSize=\"%5\" windowType=\"%6\" startTime=\"%7\" duration=\"%8\" sampleRate=\"%9\"")
-        .arg(encodeEntities(m_id))
-        .arg(encodeEntities(m_pluginVersion))
-        .arg(encodeEntities(m_program))
-        .arg(m_stepSize)
-        .arg(m_blockSize)
-        .arg(encodeEntities(Window<float>::getNameForType(m_windowType).c_str()))
-        .arg(encodeEntities(m_startTime.toString().c_str()))
-        .arg(encodeEntities(m_duration.toString().c_str()))
-        .arg(m_sampleRate);
-
-    if (extraAttributes != "") {
-        out << " " << extraAttributes;
-    }
-
-    if (haveContent) {
-
-        out << ">\n";
-
-        for (ParameterMap::const_iterator i = m_parameters.begin();
-             i != m_parameters.end(); ++i) {
-            out << indent << "  "
-                << QString("<parameter name=\"%1\" value=\"%2\"/>\n")
-                .arg(encodeEntities(i->first))
-                .arg(i->second);
-        }
-        
-        for (ConfigurationMap::const_iterator i = m_configuration.begin();
-             i != m_configuration.end(); ++i) {
-            out << indent << "  "
-                << QString("<configuration name=\"%1\" value=\"%2\"/>\n")
-                .arg(encodeEntities(i->first))
-                .arg(encodeEntities(i->second));
-        }
-
-        out << indent << "</transform>\n";
-
-    } else {
-
-        out << "/>\n";
-    }
-}
-
-void
-Transform::setFromXmlAttributes(const QXmlAttributes &attrs)
-{
-    if (attrs.value("id") != "") {
-        setIdentifier(attrs.value("id"));
-    }
-
-    if (attrs.value("pluginVersion") != "") {
-        setPluginVersion(attrs.value("pluginVersion"));
-    }
-
-    if (attrs.value("program") != "") {
-        setProgram(attrs.value("program"));
-    }
-
-    if (attrs.value("stepSize") != "") {
-        setStepSize(attrs.value("stepSize").toInt());
-    }
-
-    if (attrs.value("blockSize") != "") {
-        setBlockSize(attrs.value("blockSize").toInt());
-    }
-
-    if (attrs.value("windowType") != "") {
-        setWindowType(Window<float>::getTypeForName
-                      (attrs.value("windowType").toStdString()));
-    }
-
-    if (attrs.value("startTime") != "") {
-        setStartTime(RealTime::fromString(attrs.value("startTime").toStdString()));
-    }
-
-    if (attrs.value("duration") != "") {
-        setStartTime(RealTime::fromString(attrs.value("duration").toStdString()));
-    }
-    
-    if (attrs.value("sampleRate") != "") {
-        setSampleRate(attrs.value("sampleRate").toFloat());
-    }
-}
-
--- a/plugin/transform/Transform.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +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-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
-    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/XmlExportable.h"
-#include "base/Window.h"
-#include "base/RealTime.h"
-
-#include <QString>
-
-typedef QString TransformId;
-
-class QXmlAttributes;
-
-namespace Vamp {
-    class PluginBase;
-}
-
-class Transform : public XmlExportable
-{
-public:
-    /**
-     * Construct a new Transform with default data and no identifier.
-     * The Transform object will be meaningless until some data and an
-     * identifier have been set on it.
-     *
-     * To construct a Transform for use with a particular transform
-     * identifier, use TransformFactory::getDefaultTransformFor.
-     */
-    Transform();
-
-    /**
-     * Construct a Transform by parsing the given XML data string.
-     * This is the inverse of toXml.
-     */
-    Transform(QString xml);
-
-    virtual ~Transform();
-
-    /**
-     * Compare two Transforms.  They only compare equal if every data
-     * element matches.
-     */
-    bool operator==(const Transform &);
-
-    void setIdentifier(TransformId id);
-    TransformId getIdentifier() const;
-
-    enum Type { FeatureExtraction, RealTimeEffect };
-
-    Type getType() const;
-    QString getPluginIdentifier() const;
-    QString getOutput() const;
-    
-    void setPluginIdentifier(QString pluginIdentifier);
-    void setOutput(QString output);
-
-    // Turn a plugin ID and output name into a transform ID.  Note
-    // that our pluginIdentifier is the same thing as the Vamp SDK's
-    // PluginLoader::PluginKey.
-    static TransformId getIdentifierForPluginOutput(QString pluginIdentifier,
-                                                    QString output = "");
-
-    typedef std::map<QString, float> ParameterMap;
-    
-    const ParameterMap &getParameters() const;
-    void setParameters(const ParameterMap &pm);
-    void setParameter(QString name, float value);
-
-    typedef std::map<QString, QString> ConfigurationMap;
-
-    const ConfigurationMap &getConfiguration() const;
-    void setConfiguration(const ConfigurationMap &cm);
-    void setConfigurationValue(QString name, QString value);
-
-    QString getPluginVersion() const;
-    void setPluginVersion(QString version);
-
-    QString getProgram() const;
-    void setProgram(QString program);
-    
-    size_t getStepSize() const;
-    void setStepSize(size_t s);
-    
-    size_t getBlockSize() const;
-    void setBlockSize(size_t s);
-    
-    WindowType getWindowType() const;
-    void setWindowType(WindowType type);
-    
-    RealTime getStartTime() const;
-    void setStartTime(RealTime t);
-    
-    RealTime getDuration() const; // 0 -> all
-    void setDuration(RealTime d);
-    
-    float getSampleRate() const; // 0 -> as input
-    void setSampleRate(float rate);
-
-    void toXml(QTextStream &stream, QString indent = "",
-               QString extraAttributes = "") const;
-
-    /**
-     * Set the main transform data from the given XML attributes.
-     * This does not set the parameters or configuration, which are
-     * exported to separate XML elements rather than attributes of the
-     * transform element.
-     * 
-     * Note that this only sets those attributes which are actually
-     * present in the argument.  Any attributes not defined in the
-     * attribute will remain unchanged in the Transform.  If your aim
-     * is to create a transform exactly matching the given attributes,
-     * ensure you start from an empty transform rather than one that
-     * has already been configured.
-     */
-    void setFromXmlAttributes(const QXmlAttributes &);
-
-protected:
-    TransformId m_id; // pluginid:output, that is type:soname:label:output
-    
-    static QString createIdentifier
-    (QString type, QString soName, QString label, QString output);
-
-    static void parseIdentifier
-    (QString identifier,
-     QString &type, QString &soName, QString &label, QString &output);
-
-    ParameterMap m_parameters;
-    ConfigurationMap m_configuration;
-    QString m_pluginVersion;
-    QString m_program;
-    size_t m_stepSize;
-    size_t m_blockSize;
-    WindowType m_windowType;
-    RealTime m_startTime;
-    RealTime m_duration;
-    float m_sampleRate;
-};
-
-#endif
-
--- a/plugin/transform/TransformDescription.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +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-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
-    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_DESCRIPTION_H_
-#define _TRANSFORM_DESCRIPTION_H_
-
-#include "Transform.h"
-
-#include <QString>
-
-#include <vector>
-
-/**
- * Metadata associated with a transform.
- *
- * The transform ID is the same as that used in the Transform class.
- * It 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 that creates these objects
- * should 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.
- *
- * To obtain these objects, use
- * TransformFactory::getAllTransformDescriptions and
- * TransformFactory::getTransformDescription.
- */
-
-struct TransformDescription
-{
-    TransformDescription() { }
-    TransformDescription(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 TransformDescription &od) const {
-        return
-            (name <  od.name) ||
-            (name == od.name && identifier < od.identifier);
-    };
-};
-
-typedef std::vector<TransformDescription> TransformList;
-
-#endif
--- a/plugin/transform/TransformFactory.cpp	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,776 +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 "plugin/FeatureExtractionPluginFactory.h"
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/RealTimePluginInstance.h"
-#include "plugin/PluginXml.h"
-
-#include "vamp-sdk/Plugin.h"
-#include "vamp-sdk/PluginHostAdapter.h"
-#include "vamp-sdk/hostext/PluginWrapper.h"
-
-#include <iostream>
-#include <set>
-
-#include <QRegExp>
-#include <QTextStream>
-
-TransformFactory *
-TransformFactory::m_instance = new TransformFactory;
-
-TransformFactory *
-TransformFactory::getInstance()
-{
-    return m_instance;
-}
-
-TransformFactory::~TransformFactory()
-{
-}
-
-TransformList
-TransformFactory::getAllTransformDescriptions()
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    std::set<TransformDescription> dset;
-    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
-	 i != m_transforms.end(); ++i) {
-//        std::cerr << "inserting transform into set: id = " << i->second.identifier.toStdString() << std::endl;
-	dset.insert(i->second);
-    }
-
-    TransformList list;
-    for (std::set<TransformDescription>::const_iterator i = dset.begin();
-	 i != dset.end(); ++i) {
-//        std::cerr << "inserting transform into list: id = " << i->identifier.toStdString() << std::endl;
-	list.push_back(*i);
-    }
-
-    return list;
-}
-
-TransformDescription
-TransformFactory::getTransformDescription(TransformId id)
-{
-    if (m_transforms.empty()) populateTransforms();
-
-    if (m_transforms.find(id) == m_transforms.end()) {
-        return TransformDescription();
-    }
-
-    return m_transforms[id];
-}
-
-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) {
-
-        TransformDescription desc = i->second;
-
-        QString td = desc.name;
-        QString tn = td.section(": ", 0, 0);
-        QString pn = desc.identifier.section(":", 1, 1);
-
-        if (pluginSources.find(tn) != pluginSources.end()) {
-            if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) {
-                ++names[tn];
-            }
-        } else {
-            ++names[tn];
-            pluginSources[tn] = pn;
-            pluginMakers[tn] = desc.maker;
-        }
-    }
-
-    std::map<QString, int> counts;
-    m_transforms.clear();
-
-    for (TransformDescriptionMap::iterator i = transforms.begin();
-         i != transforms.end(); ++i) {
-
-        TransformDescription desc = i->second;
-	QString identifier = desc.identifier;
-        QString maker = desc.maker;
-
-        QString td = desc.name;
-        QString tn = td.section(": ", 0, 0);
-        QString to = td.section(": ", 1);
-
-	if (names[tn] > 1) {
-            maker.replace(QRegExp(tr(" [\\(<].*$")), "");
-	    tn = QString("%1 [%2]").arg(tn).arg(maker);
-	}
-
-        if (to != "") {
-            desc.name = QString("%1: %2").arg(tn).arg(to);
-        } else {
-            desc.name = tn;
-        }
-
-	m_transforms[identifier] = desc;
-    }	    
-}
-
-void
-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, 44100);
-
-	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() << " friendly name: " << friendlyName.toStdString() << std::endl;
-
-	    transforms[transformId] = 
-                TransformDescription(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] = 
-                    TransformDescription(tr("Effects Data"),
-                                         category,
-                                         transformId,
-                                         userName,
-                                         userName,
-                                         description,
-                                         maker,
-                                         units,
-                                         configurable);
-            }
-        }
-
-        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
-
-            if (descriptor->audioOutputPortCount > 0) {
-
-                QString transformId = QString("%1:A").arg(pluginId);
-                QString type = tr("Effects");
-
-                QString description = tr("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] =
-                    TransformDescription(type,
-                                         category,
-                                         transformId,
-                                         pluginName,
-                                         pluginName,
-                                         description,
-                                         maker,
-                                         "",
-                                         configurable);
-            }
-        }
-    }
-}
-
-
-Transform
-TransformFactory::getDefaultTransformFor(TransformId id, size_t rate)
-{
-    Transform t;
-    t.setIdentifier(id);
-    if (rate != 0) t.setSampleRate(rate);
-
-    Vamp::PluginBase *plugin = instantiateDefaultPluginFor(id, rate);
-
-    if (plugin) {
-        t.setPluginVersion(QString("%1").arg(plugin->getPluginVersion()));
-        setParametersFromPlugin(t, plugin);
-        makeContextConsistentWithPlugin(t, plugin);
-        delete plugin;
-    }
-
-    return t;
-}
-
-Vamp::PluginBase *
-TransformFactory::instantiatePluginFor(const Transform &transform)
-{
-    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
-        (transform.getIdentifier(), transform.getSampleRate());
-    if (plugin) {
-        setPluginParameters(transform, plugin);
-    }
-    return plugin;
-}
-
-Vamp::PluginBase *
-TransformFactory::instantiateDefaultPluginFor(TransformId identifier, size_t rate)
-{
-    Transform t;
-    t.setIdentifier(identifier);
-    if (rate == 0) rate = 44100;
-    QString pluginId = t.getPluginIdentifier();
-
-    Vamp::PluginBase *plugin = 0;
-
-    if (t.getType() == Transform::FeatureExtraction) {
-
-        FeatureExtractionPluginFactory *factory = 
-            FeatureExtractionPluginFactory::instanceFor(pluginId);
-
-        plugin = factory->instantiatePlugin(pluginId, rate);
-
-    } else {
-
-        RealTimePluginFactory *factory = 
-            RealTimePluginFactory::instanceFor(pluginId);
-            
-        plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1);
-    }
-
-    return plugin;
-}
-
-Vamp::Plugin *
-TransformFactory::downcastVampPlugin(Vamp::PluginBase *plugin)
-{
-    Vamp::Plugin *vp = dynamic_cast<Vamp::Plugin *>(plugin);
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl;
-        vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin); //!!! why?
-}
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl;
-        vp = dynamic_cast<Vamp::HostExt::PluginWrapper *>(plugin); //!!! no, I mean really why?
-    }
-    if (!vp) {
-//        std::cerr << "makeConsistentWithPlugin: not a Vamp::HostExt::PluginWrapper" << std::endl;
-    }
-    return vp;
-}
-
-bool
-TransformFactory::haveTransform(TransformId identifier)
-{
-    if (m_transforms.empty()) populateTransforms();
-    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 "";
-}
-
-Vamp::Plugin::InputDomain
-TransformFactory::getTransformInputDomain(TransformId identifier)
-{
-    Transform transform;
-    transform.setIdentifier(identifier);
-
-    if (transform.getType() != Transform::FeatureExtraction) {
-        return Vamp::Plugin::TimeDomain;
-    }
-
-    Vamp::Plugin *plugin =
-        downcastVampPlugin(instantiateDefaultPluginFor(identifier, 0));
-
-    if (plugin) {
-        Vamp::Plugin::InputDomain d = plugin->getInputDomain();
-        delete plugin;
-        return d;
-    }
-
-    return Vamp::Plugin::TimeDomain;
-}
-
-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, 44100);
-        if (!plugin) return false;
-
-        min = plugin->getMinChannelCount();
-        max = plugin->getMaxChannelCount();
-        delete plugin;
-
-        return true;
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-
-        // don't need to instantiate
-
-        const RealTimePluginDescriptor *descriptor = 
-            RealTimePluginFactory::instanceFor(id)->
-            getPluginDescriptor(id);
-        if (!descriptor) return false;
-
-        min = descriptor->audioInputPortCount;
-        max = descriptor->audioInputPortCount;
-
-        return true;
-    }
-
-    return false;
-}
-
-void
-TransformFactory::setParametersFromPlugin(Transform &transform,
-                                          Vamp::PluginBase *plugin)
-{
-    Transform::ParameterMap pmap;
-
-    //!!! record plugin & API version
-
-    //!!! check that this is the right plugin!
-
-    Vamp::PluginBase::ParameterList parameters =
-        plugin->getParameterDescriptors();
-
-    for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin();
-         i != parameters.end(); ++i) {
-        pmap[i->identifier.c_str()] = plugin->getParameter(i->identifier);
-    }
-
-    transform.setParameters(pmap);
-
-    if (plugin->getPrograms().empty()) {
-        transform.setProgram("");
-    } else {
-        transform.setProgram(plugin->getCurrentProgram().c_str());
-    }
-
-    RealTimePluginInstance *rtpi =
-        dynamic_cast<RealTimePluginInstance *>(plugin);
-
-    Transform::ConfigurationMap cmap;
-
-    if (rtpi) {
-
-        RealTimePluginInstance::ConfigurationPairMap configurePairs =
-            rtpi->getConfigurePairs();
-
-        for (RealTimePluginInstance::ConfigurationPairMap::const_iterator i
-                 = configurePairs.begin(); i != configurePairs.end(); ++i) {
-            cmap[i->first.c_str()] = i->second.c_str();
-        }
-    }
-
-    transform.setConfiguration(cmap);
-}
-
-void
-TransformFactory::setPluginParameters(const Transform &transform,
-                                      Vamp::PluginBase *plugin)
-{
-    //!!! check plugin & API version (see e.g. PluginXml::setParameters)
-
-    //!!! check that this is the right plugin!
-
-    RealTimePluginInstance *rtpi =
-        dynamic_cast<RealTimePluginInstance *>(plugin);
-
-    if (rtpi) {
-        const Transform::ConfigurationMap &cmap = transform.getConfiguration();
-        for (Transform::ConfigurationMap::const_iterator i = cmap.begin();
-             i != cmap.end(); ++i) {
-            rtpi->configure(i->first.toStdString(), i->second.toStdString());
-        }
-    }
-
-    if (transform.getProgram() != "") {
-        plugin->selectProgram(transform.getProgram().toStdString());
-    }
-
-    const Transform::ParameterMap &pmap = transform.getParameters();
-
-    Vamp::PluginBase::ParameterList parameters =
-        plugin->getParameterDescriptors();
-
-    for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin();
-         i != parameters.end(); ++i) {
-        QString key = i->identifier.c_str();
-        Transform::ParameterMap::const_iterator pmi = pmap.find(key);
-        if (pmi != pmap.end()) {
-            plugin->setParameter(i->identifier, pmi->second);
-        }
-    }
-}
-
-void
-TransformFactory::makeContextConsistentWithPlugin(Transform &transform,
-                                                  Vamp::PluginBase *plugin)
-{
-    const Vamp::Plugin *vp = downcastVampPlugin(plugin);
-
-    if (!vp) {
-        // time domain input for real-time effects plugin
-        if (!transform.getBlockSize()) {
-            if (!transform.getStepSize()) transform.setStepSize(1024);
-            transform.setBlockSize(transform.getStepSize());
-        } else {
-            transform.setStepSize(transform.getBlockSize());
-        }
-    } else {
-        Vamp::Plugin::InputDomain domain = vp->getInputDomain();
-        if (!transform.getStepSize()) {
-            transform.setStepSize(vp->getPreferredStepSize());
-        }
-        if (!transform.getBlockSize()) {
-            transform.setBlockSize(vp->getPreferredBlockSize());
-        }
-        if (!transform.getBlockSize()) {
-            transform.setBlockSize(1024);
-        }
-        if (!transform.getStepSize()) {
-            if (domain == Vamp::Plugin::FrequencyDomain) {
-//                std::cerr << "frequency domain, step = " << blockSize/2 << std::endl;
-                transform.setStepSize(transform.getBlockSize()/2);
-            } else {
-//                std::cerr << "time domain, step = " << blockSize/2 << std::endl;
-                transform.setStepSize(transform.getBlockSize());
-            }
-        }
-    }
-}
-
-QString
-TransformFactory::getPluginConfigurationXml(const Transform &t)
-{
-    QString xml;
-
-    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
-        (t.getIdentifier(), 0);
-    if (!plugin) {
-        std::cerr << "TransformFactory::getPluginConfigurationXml: "
-                  << "Unable to instantiate plugin for transform \""
-                  << t.getIdentifier().toStdString() << "\"" << std::endl;
-        return xml;
-    }
-
-    setPluginParameters(t, plugin);
-
-    QTextStream out(&xml);
-    PluginXml(plugin).toXml(out);
-    delete plugin;
-
-    return xml;
-}
-
-void
-TransformFactory::setParametersFromPluginConfigurationXml(Transform &t,
-                                                          QString xml)
-{
-    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
-        (t.getIdentifier(), 0);
-    if (!plugin) {
-        std::cerr << "TransformFactory::setParametersFromPluginConfigurationXml: "
-                  << "Unable to instantiate plugin for transform \""
-                  << t.getIdentifier().toStdString() << "\"" << std::endl;
-        return;
-    }
-
-    PluginXml(plugin).setParametersFromXml(xml);
-    setParametersFromPlugin(t, plugin);
-    delete plugin;
-}
-
--- a/plugin/transform/TransformFactory.h	Wed Mar 12 17:42:56 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +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-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
-    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 "TransformDescription.h"
-
-#include <vamp-sdk/Plugin.h>
-
-#include <QObject>
-
-#include <map>
-#include <set>
-
-class TransformFactory : public QObject
-{
-    Q_OBJECT
-
-public:
-    virtual ~TransformFactory();
-
-    static TransformFactory *getInstance();
-
-    TransformList getAllTransformDescriptions();
-    TransformDescription getTransformDescription(TransformId id);
-
-    std::vector<QString> getAllTransformTypes();
-    std::vector<QString> getTransformCategories(QString transformType);
-    std::vector<QString> getTransformMakers(QString transformType);
-
-    /**
-     * Return true if the given transform is known.
-     */
-    bool haveTransform(TransformId identifier);
-
-    /**
-     * A single transform ID can lead to many possible Transforms,
-     * with different parameters and execution context settings.
-     * Return the default one for the given transform.
-     */
-    Transform getDefaultTransformFor(TransformId identifier, size_t rate = 0);
-
-    /**
-     * 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);
-
-    Vamp::Plugin::InputDomain getTransformInputDomain(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);
-
-    /**
-     * Load an appropriate plugin for the given transform and set the
-     * parameters, program and configuration strings on that plugin
-     * from the Transform object.
-     *
-     * Note that this requires that the transform has a meaningful
-     * sample rate set, as that is used as the rate for the plugin.  A
-     * Transform can legitimately have rate set at zero (= "use the
-     * rate of the input source"), so the caller will need to test for
-     * this case.
-     *
-     * Returns the plugin thus loaded.  This will be a
-     * Vamp::PluginBase, but not necessarily a Vamp::Plugin (only if
-     * the transform was a feature-extraction type -- call
-     * downcastVampPlugin if you only want Vamp::Plugins).  Returns
-     * NULL if no suitable plugin was available.
-     *
-     * The returned plugin is owned by the caller, and should be
-     * deleted (using "delete") when no longer needed.
-     */
-    Vamp::PluginBase *instantiatePluginFor(const Transform &transform);
-
-    /**
-     * Convert a Vamp::PluginBase to a Vamp::Plugin, if it is one.
-     * Return NULL otherwise.  This ill-fitting convenience function
-     * is really just a dynamic_cast wrapper.
-     */
-    Vamp::Plugin *downcastVampPlugin(Vamp::PluginBase *);
-
-    /**
-     * Set the plugin parameters, program and configuration strings on
-     * the given Transform object from the given plugin instance.
-     * Note that no check is made whether the plugin is actually the
-     * "correct" one for the transform.
-     */
-    void setParametersFromPlugin(Transform &transform, Vamp::PluginBase *plugin);
-
-    /**
-     * Set the parameters, program and configuration strings on the
-     * given plugin from the given Transform object.
-     */
-    void setPluginParameters(const Transform &transform, Vamp::PluginBase *plugin);
-    
-    /**
-     * If the given Transform object has no processing step and block
-     * sizes set, set them to appropriate defaults for the given
-     * plugin.
-     */
-    void makeContextConsistentWithPlugin(Transform &transform, Vamp::PluginBase *plugin); 
-
-    /**
-     * Retrieve a <plugin ... /> XML fragment that describes the
-     * plugin parameters, program and configuration data for the given
-     * transform.
-     *
-     * This function is provided for backward compatibility only.  Use
-     * Transform::toXml where compatibility with PluginXml
-     * descriptions of transforms is not required.
-     */
-    QString getPluginConfigurationXml(const Transform &transform);
-
-    /**
-     * Set the plugin parameters, program and configuration strings on
-     * the given Transform object from the given <plugin ... /> XML
-     * fragment.
-     *
-     * This function is provided for backward compatibility only.  Use
-     * Transform(QString) where compatibility with PluginXml
-     * descriptions of transforms is not required.
-     */
-    void setParametersFromPluginConfigurationXml(Transform &transform,
-                                                 QString xml);
-
-protected:
-    typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
-    TransformDescriptionMap m_transforms;
-
-    void populateTransforms();
-    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
-    void populateRealTimePlugins(TransformDescriptionMap &);
-
-    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, size_t rate);
-
-    static TransformFactory *m_instance;
-};
-
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,643 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FeatureExtractionModelTransformer.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/PluginXml.h"
+#include "vamp-sdk/Plugin.h"
+
+#include "data/model/Model.h"
+#include "base/Window.h"
+#include "base/Exceptions.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 "TransformFactory.h"
+
+#include <iostream>
+
+FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
+                                                                     const Transform &transform) :
+    ModelTransformer(in, transform),
+    m_plugin(0),
+    m_descriptor(0),
+    m_outputFeatureNo(0)
+{
+//    std::cerr << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId.toStdString() << ", outputName " << m_transform.getOutput().toStdString() << std::endl;
+
+    QString pluginId = transform.getPluginIdentifier();
+
+    FeatureExtractionPluginFactory *factory =
+	FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+        m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
+	return;
+    }
+
+    DenseTimeValueModel *input = getConformingInput();
+    if (!input) {
+        m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
+        return;
+    }
+
+    m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
+    if (!m_plugin) {
+        m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
+	return;
+    }
+
+    TransformFactory::getInstance()->makeContextConsistentWithPlugin
+        (m_transform, m_plugin);
+
+    TransformFactory::getInstance()->setPluginParameters
+        (m_transform, m_plugin);
+
+    size_t channelCount = input->getChannelCount();
+    if (m_plugin->getMaxChannelCount() < channelCount) {
+	channelCount = 1;
+    }
+    if (m_plugin->getMinChannelCount() > channelCount) {
+        m_message = tr("Cannot provide enough channels to feature extraction plugin \"%1\" (plugin min is %2, max %3; input model has %4)")
+            .arg(pluginId)
+            .arg(m_plugin->getMinChannelCount())
+            .arg(m_plugin->getMaxChannelCount())
+            .arg(input->getChannelCount());
+	return;
+    }
+
+    std::cerr << "Initialising feature extraction plugin with channels = "
+              << channelCount << ", step = " << m_transform.getStepSize()
+              << ", block = " << m_transform.getBlockSize() << std::endl;
+
+    if (!m_plugin->initialise(channelCount,
+                              m_transform.getStepSize(),
+                              m_transform.getBlockSize())) {
+
+        size_t pstep = m_transform.getStepSize();
+        size_t pblock = m_transform.getBlockSize();
+
+        m_transform.setStepSize(0);
+        m_transform.setBlockSize(0);
+        TransformFactory::getInstance()->makeContextConsistentWithPlugin
+            (m_transform, m_plugin);
+
+        if (m_transform.getStepSize() != pstep ||
+            m_transform.getBlockSize() != pblock) {
+            
+            if (!m_plugin->initialise(channelCount,
+                                      m_transform.getStepSize(),
+                                      m_transform.getBlockSize())) {
+
+                m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
+                return;
+
+            } else {
+
+                m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead")
+                    .arg(pluginId)
+                    .arg(pstep)
+                    .arg(pblock)
+                    .arg(m_transform.getStepSize())
+                    .arg(m_transform.getBlockSize());
+            }
+
+        } else {
+
+            m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
+            return;
+        }
+    }
+
+    if (m_transform.getPluginVersion() != "") {
+        QString pv = QString("%1").arg(m_plugin->getPluginVersion());
+        if (pv != m_transform.getPluginVersion()) {
+            QString vm = tr("Transform was configured for version %1 of plugin \"%2\", but the plugin being used is version %3")
+                .arg(m_transform.getPluginVersion())
+                .arg(pluginId)
+                .arg(pv);
+            if (m_message != "") {
+                m_message = QString("%1; %2").arg(vm).arg(m_message);
+            } else {
+                m_message = vm;
+            }
+        }
+    }
+
+    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
+
+    if (outputs.empty()) {
+        m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId);
+	return;
+    }
+    
+    for (size_t i = 0; i < outputs.size(); ++i) {
+	if (m_transform.getOutput() == "" ||
+            outputs[i].identifier == m_transform.getOutput().toStdString()) {
+	    m_outputFeatureNo = i;
+	    m_descriptor = new Vamp::Plugin::OutputDescriptor
+		(outputs[i]);
+	    break;
+	}
+    }
+
+    if (!m_descriptor) {
+        m_message = tr("Plugin \"%1\" has no output named \"%2\"")
+            .arg(pluginId)
+            .arg(m_transform.getOutput());
+	return;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer: output sample type "
+//	      << m_descriptor->sampleType << std::endl;
+
+    int binCount = 1;
+    float minValue = 0.0, maxValue = 0.0;
+    bool haveExtents = false;
+    
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer: output bin count "
+//	      << binCount << std::endl;
+
+    if (binCount > 0 && m_descriptor->hasKnownExtents) {
+	minValue = m_descriptor->minValue;
+	maxValue = m_descriptor->maxValue;
+        haveExtents = true;
+    }
+
+    size_t modelRate = 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_transform.getStepSize();
+	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;
+    }
+
+    if (m_output) m_output->setSourceModel(input);
+}
+
+FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
+{
+    std::cerr << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << std::endl;
+    delete m_plugin;
+    delete m_descriptor;
+}
+
+DenseTimeValueModel *
+FeatureExtractionModelTransformer::getConformingInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+FeatureExtractionModelTransformer::run()
+{
+    DenseTimeValueModel *input = getConformingInput();
+    if (!input) return;
+
+    if (!m_output) return;
+
+    while (!input->isReady()) {
+/*
+        if (dynamic_cast<WaveFileModel *>(input)) {
+            std::cerr << "FeatureExtractionModelTransformer::run: Model is not ready, but it's not a WaveFileModel (it's a " << typeid(input).name() << "), so that's OK" << std::endl;
+            sleep(2);
+            break; // no need to wait
+        }
+*/
+        std::cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    size_t sampleRate = 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_transform.getBlockSize() + 2];
+    }
+
+    size_t stepSize = m_transform.getStepSize();
+    size_t blockSize = m_transform.getBlockSize();
+
+    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
+                                  (getConformingInput(),
+                                   channelCount == 1 ? m_input.getChannel() : ch,
+                                   m_transform.getWindowType(),
+                                   blockSize,
+                                   stepSize,
+                                   blockSize,
+                                   false,
+                                   StorageAdviser::PrecisionCritical);
+            if (!model->isOK()) {
+                delete model;
+                setCompletion(100);
+                //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either
+                throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer");
+            }
+            model->resume();
+            fftModels.push_back(model);
+        }
+    }
+
+    long startFrame = m_input.getModel()->getStartFrame();
+    long   endFrame = m_input.getModel()->getEndFrame();
+
+    RealTime contextStartRT = m_transform.getStartTime();
+    RealTime contextDurationRT = m_transform.getDuration();
+
+    long contextStart =
+        RealTime::realTime2Frame(contextStartRT, sampleRate);
+
+    long contextDuration =
+        RealTime::realTime2Frame(contextDurationRT, sampleRate);
+
+    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(blockSize)/2 >
+                contextStart + contextDuration) break;
+        } else {
+            if (blockFrame >= 
+                contextStart + contextDuration) break;
+        }
+
+//	std::cerr << "FeatureExtractionModelTransformer::run: blockFrame "
+//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
+//                  << blockSize << std::endl;
+
+	long completion =
+	    (((blockFrame - contextStart) / stepSize) * 99) /
+	    (contextDuration / stepSize);
+
+	// channelCount is either m_input.getModel()->channelCount or 1
+
+        if (frequencyDomain) {
+            for (size_t ch = 0; ch < channelCount; ++ch) {
+                int column = (blockFrame - startFrame) / stepSize;
+                for (size_t i = 0; i <= blockSize/2; ++i) {
+                    fftModels[ch]->getValuesAt
+                        (column, i, buffers[ch][i*2], buffers[ch][i*2+1]);
+                }
+            }
+        } else {
+            getFrames(channelCount, blockFrame, blockSize, buffers);
+        }
+
+	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 += stepSize;
+    }
+
+    if (m_abandoned) return;
+
+    Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
+
+    for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
+	Vamp::Plugin::Feature feature =
+	    features[m_outputFeatureNo][fi];
+	addFeature(blockFrame, feature);
+    }
+
+    if (frequencyDomain) {
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            delete fftModels[ch];
+        }
+    }
+
+    setCompletion(100);
+}
+
+void
+FeatureExtractionModelTransformer::getFrames(int channelCount,
+                                             long startFrame, long size,
+                                             float **buffers)
+{
+    long offset = 0;
+
+    if (startFrame < 0) {
+        for (int c = 0; c < channelCount; ++c) {
+            for (int i = 0; i < size && startFrame + i < 0; ++i) {
+                buffers[c][i] = 0.0f;
+            }
+        }
+        offset = -startFrame;
+        size -= offset;
+        if (size <= 0) return;
+        startFrame = 0;
+    }
+
+    DenseTimeValueModel *input = getConformingInput();
+    if (!input) return;
+    
+    long got = 0;
+
+    if (channelCount == 1) {
+
+        got = input->getData(m_input.getChannel(), startFrame, size,
+                             buffers[0] + offset);
+
+        if (m_input.getChannel() == -1 && input->getChannelCount() > 1) {
+            // use mean instead of sum, as plugin input
+            float cc = float(input->getChannelCount());
+            for (long i = 0; i < size; ++i) {
+                buffers[0][i + offset] /= cc;
+            }
+        }
+
+    } else {
+
+        float **writebuf = buffers;
+        if (offset > 0) {
+            writebuf = new float *[channelCount];
+            for (int i = 0; i < channelCount; ++i) {
+                writebuf[i] = buffers[i] + offset;
+            }
+        }
+
+        got = input->getData(0, channelCount-1, startFrame, size, writebuf);
+
+        if (writebuf != buffers) delete[] writebuf;
+    }
+
+    while (got < size) {
+        for (int c = 0; c < channelCount; ++c) {
+            buffers[c][got + offset] = 0.0;
+        }
+        ++got;
+    }
+}
+
+void
+FeatureExtractionModelTransformer::addFeature(size_t blockFrame,
+					     const Vamp::Plugin::Feature &feature)
+{
+    size_t inputRate = m_input.getModel()->getSampleRate();
+
+//    std::cerr << "FeatureExtractionModelTransformer::addFeature("
+//	      << blockFrame << ")" << std::endl;
+
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+    size_t frame = blockFrame;
+
+    if (m_descriptor->sampleType ==
+	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	if (!feature.hasTimestamp) {
+	    std::cerr
+		<< "WARNING: FeatureExtractionModelTransformer::addFeature: "
+		<< "Feature has variable sample rate but no timestamp!"
+		<< std::endl;
+	    return;
+	} else {
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
+	}
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
+
+	if (feature.hasTimestamp) {
+	    //!!! warning: sampleRate may be non-integral
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp,
+                                                   lrintf(m_descriptor->sampleRate));
+	} else {
+	    frame = m_output->getEndFrame();
+	}
+    }
+	
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model =
+            getConformingOutput<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 =
+            getConformingOutput<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];
+        if (velocity < 0) velocity = 127;
+        if (velocity > 127) velocity = 127;
+
+        NoteModel *model = getConformingOutput<NoteModel>();
+        if (!model) return;
+
+        model->addPoint(NoteModel::Point(frame, pitch,
+                                         lrintf(duration),
+                                         velocity / 127.f,
+                                         feature.label.c_str()));
+	
+    } else {
+	
+	DenseThreeDimensionalModel::Column values = feature.values;
+	
+	EditableDenseThreeDimensionalModel *model =
+            getConformingOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+
+	model->setColumn(frame / model->getResolution(), values);
+    }
+}
+
+void
+FeatureExtractionModelTransformer::setCompletion(int completion)
+{
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionModelTransformer::setCompletion("
+//              << completion << ")" << std::endl;
+
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model =
+            getConformingOutput<SparseOneDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion, true); //!!!m_context.updates);
+
+    } else if (binCount == 1) {
+
+	SparseTimeValueModel *model =
+            getConformingOutput<SparseTimeValueModel>();
+	if (!model) return;
+	model->setCompletion(completion, true); //!!!m_context.updates);
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	NoteModel *model =
+            getConformingOutput<NoteModel>();
+	if (!model) return;
+	model->setCompletion(completion, true); //!!!m_context.updates);
+
+    } else {
+
+	EditableDenseThreeDimensionalModel *model =
+            getConformingOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion, true); //!!!m_context.updates);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/FeatureExtractionModelTransformer.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,65 @@
+/* -*- 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 "ModelTransformer.h"
+
+#include <QString>
+
+#include <vamp-sdk/Plugin.h>
+
+#include <iostream>
+
+class DenseTimeValueModel;
+
+class FeatureExtractionModelTransformer : public ModelTransformer
+{
+    Q_OBJECT
+
+public:
+    FeatureExtractionModelTransformer(Input input,
+                                      const Transform &transform);
+    virtual ~FeatureExtractionModelTransformer();
+
+protected:
+    virtual void run();
+
+    Vamp::Plugin *m_plugin;
+    Vamp::Plugin::OutputDescriptor *m_descriptor;
+    int m_outputFeatureNo;
+
+    void addFeature(size_t blockFrame,
+		    const Vamp::Plugin::Feature &feature);
+
+    void setCompletion(int);
+
+    void getFrames(int channelCount, long startFrame, long size,
+                   float **buffer);
+
+    // just casts
+    DenseTimeValueModel *getConformingInput();
+    template <typename ModelClass> ModelClass *getConformingOutput() {
+	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
+	if (!mc) {
+	    std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl;
+	}
+	return mc;
+    }
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/ModelTransformer.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,33 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ModelTransformer.h"
+
+ModelTransformer::ModelTransformer(Input input, const Transform &transform) :
+    m_transform(transform),
+    m_input(input),
+    m_output(0),
+    m_detached(false),
+    m_abandoned(false)
+{
+}
+
+ModelTransformer::~ModelTransformer()
+{
+    m_abandoned = true;
+    wait();
+    if (!m_detached) delete m_output;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/ModelTransformer.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,111 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _TRANSFORMER_H_
+#define _TRANSFORMER_H_
+
+#include "base/Thread.h"
+
+#include "data/model/Model.h"
+
+#include "Transform.h"
+
+/**
+ * A ModelTransformer turns one data model into another.
+ *
+ * Typically in this application, a ModelTransformer might have a
+ * DenseTimeValueModel as its input (e.g. an audio waveform) and a
+ * SparseOneDimensionalModel (e.g. detected beats) as its output.
+ *
+ * The ModelTransformer typically runs in the background, as a
+ * separate thread populating the output model.  The model is
+ * available to the user of the ModelTransformer immediately, but may
+ * be initially empty until the background thread has populated it.
+ */
+
+class ModelTransformer : public Thread
+{
+public:
+    virtual ~ModelTransformer();
+
+    class Input {
+    public:
+        Input(Model *m) : m_model(m), m_channel(-1) { }
+        Input(Model *m, int c) : m_model(m), m_channel(c) { }
+
+        Model *getModel() const { return m_model; }
+        void setModel(Model *m) { m_model = m; }
+
+        int getChannel() const { return m_channel; }
+        void setChannel(int c) { m_channel = c; }
+
+    protected:
+        Model *m_model;
+        int m_channel;
+    };
+
+    /**
+     * Hint to the processing thread that it should give up, for
+     * example because the process is going to exit or we want to get
+     * rid of the input model.  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; }
+
+    /**
+     * Return the input model for the transform.
+     */
+    Model *getInputModel()  { return m_input.getModel(); }
+
+    /**
+     * Return the input channel spec for the transform.
+     */
+    int getInputChannel() { return m_input.getChannel(); }
+
+    /**
+     * Return the output model created by the transform.  Returns a
+     * null model if the transform could not be initialised; an error
+     * message may be available via getMessage() in this situation.
+     */
+    Model *getOutputModel() { return m_output; }
+
+    /**
+     * Return the output model, also detaching it from the transformer
+     * so that it will not be deleted when the transformer is.  The
+     * caller takes ownership of the model.
+     */
+    Model *detachOutputModel() { m_detached = true; return m_output; }
+
+    /**
+     * Return a warning or error message.  If getOutputModel returned
+     * a null pointer, this should contain a fatal error message for
+     * the transformer; otherwise it may contain a warning to show to
+     * the user about e.g. suboptimal block size or whatever.  
+     */
+    QString getMessage() const { return m_message; }
+
+protected:
+    ModelTransformer(Input input, const Transform &transform);
+
+    Transform m_transform;
+    Input m_input; // I don't own the model in this
+    Model *m_output; // I own this, unless...
+    bool m_detached; // ... this is true.
+    bool m_abandoned;
+    QString m_message;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/ModelTransformerFactory.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,450 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ModelTransformerFactory.h"
+
+#include "FeatureExtractionModelTransformer.h"
+#include "RealTimeEffectModelTransformer.h"
+
+#include "TransformFactory.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/PluginXml.h"
+
+#include "widgets/PluginParameterDialog.h"
+
+#include "data/model/DenseTimeValueModel.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+
+#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
+
+#include <iostream>
+#include <set>
+
+#include <QRegExp>
+
+ModelTransformerFactory *
+ModelTransformerFactory::m_instance = new ModelTransformerFactory;
+
+ModelTransformerFactory *
+ModelTransformerFactory::getInstance()
+{
+    return m_instance;
+}
+
+ModelTransformerFactory::~ModelTransformerFactory()
+{
+}
+
+bool
+ModelTransformerFactory::getChannelRange(TransformId identifier,
+                                         Vamp::PluginBase *plugin,
+                                         int &minChannels, int &maxChannels)
+{
+    Vamp::Plugin *vp = 0;
+    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
+        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
+        minChannels = vp->getMinChannelCount();
+        maxChannels = vp->getMaxChannelCount();
+        return true;
+    } else {
+        return TransformFactory::getInstance()->
+            getTransformChannelRange(identifier, minChannels, maxChannels);
+    }
+}
+
+ModelTransformer::Input
+ModelTransformerFactory::getConfigurationForTransform(Transform &transform,
+                                                      const std::vector<Model *> &candidateInputModels,
+                                                      Model *defaultInputModel,
+                                                      AudioCallbackPlaySource *source,
+                                                      size_t startFrame,
+                                                      size_t duration)
+{
+    ModelTransformer::Input input(0);
+
+    if (candidateInputModels.empty()) return input;
+
+    //!!! 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];
+    QStringList candidateModelNames;
+    QString defaultModelName;
+    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);
+        if (candidateInputModels[i] == defaultInputModel) {
+            defaultModelName = modelName;
+        }
+    }
+
+    QString id = transform.getPluginIdentifier();
+    QString output = transform.getOutput();
+    QString outputLabel = "";
+    QString outputDescription = "";
+    
+    bool ok = false;
+    QString configurationXml = m_lastConfigurations[transform.getIdentifier()];
+
+    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) {
+
+        // Ensure block size etc are valid
+        TransformFactory::getInstance()->
+            makeContextConsistentWithPlugin(transform, plugin);
+
+        // Prepare the plugin with any existing parameters already
+        // found in the transform
+        TransformFactory::getInstance()->
+            setPluginParameters(transform, plugin);
+        
+        // For this interactive usage, we want to override those with
+        // whatever the user chose last time around
+        PluginXml(plugin).setParametersFromXml(configurationXml);
+
+        int sourceChannels = 1;
+        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
+            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
+                ->getChannelCount();
+        }
+
+        int minChannels = 1, maxChannels = sourceChannels;
+        getChannelRange(transform.getIdentifier(), plugin,
+                        minChannels, maxChannels);
+
+        int targetChannels = sourceChannels;
+        if (!effect) {
+            if (sourceChannels < minChannels) targetChannels = minChannels;
+            if (sourceChannels > maxChannels) targetChannels = maxChannels;
+        }
+
+        int defaultChannel = -1; //!!! no longer saved! [was context.channel]
+
+        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
+
+        if (candidateModelNames.size() > 1 && !generator) {
+            dialog->setCandidateInputModels(candidateModelNames,
+                                            defaultModelName);
+        }
+
+        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;
+        }
+        
+        // Write parameters back to transform object
+        TransformFactory::getInstance()->
+            setParametersFromPlugin(transform, plugin);
+
+        input.setChannel(dialog->getChannel());
+        
+        //!!! The dialog ought to be taking & returning transform
+        //objects and input objects and stuff rather than passing
+        //around all this misc stuff, but that's for tomorrow
+        //(whenever that may be)
+
+        if (startFrame != 0 || duration != 0) {
+            if (dialog->getSelectionOnly()) {
+                transform.setStartTime(RealTime::frame2RealTime
+                                       (startFrame, inputModel->getSampleRate()));
+                transform.setDuration(RealTime::frame2RealTime
+                                      (duration, inputModel->getSampleRate()));
+            }
+        }
+
+        size_t stepSize = 0, blockSize = 0;
+        WindowType windowType = HanningWindow;
+
+        dialog->getProcessingParameters(stepSize,
+                                        blockSize,
+                                        windowType);
+
+        transform.setStepSize(stepSize);
+        transform.setBlockSize(blockSize);
+        transform.setWindowType(windowType);
+
+        TransformFactory::getInstance()->
+            makeContextConsistentWithPlugin(transform, plugin);
+
+        configurationXml = PluginXml(plugin).toXmlString();
+
+        delete dialog;
+
+        if (effect && source) {
+            source->setAuditioningPlugin(0); // will delete our plugin
+        } else {
+            delete plugin;
+        }
+    }
+
+    if (ok) {
+        m_lastConfigurations[transform.getIdentifier()] = configurationXml;
+        input.setModel(inputModel);
+    }
+
+    return input;
+}
+/*!!!
+PluginTransformer::ExecutionContext
+ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
+                                                Model *inputModel)
+{
+    PluginTransformer::ExecutionContext context(-1);
+
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel ? inputModel->getSampleRate() : 48000);
+
+        if (vp) {
+            context = PluginTransformer::ExecutionContext(-1, vp);
+            delete vp;
+        }
+    }
+
+    return context;
+}
+*/
+ModelTransformer *
+ModelTransformerFactory::createTransformer(const Transform &transform,
+                                           const ModelTransformer::Input &input)
+{
+    ModelTransformer *transformer = 0;
+
+    QString id = transform.getPluginIdentifier();
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        transformer =
+            new FeatureExtractionModelTransformer(input, transform);
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        transformer =
+            new RealTimeEffectModelTransformer(input, transform);
+
+    } else {
+        std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
+                  << transform.getIdentifier().toStdString() << "\"" << std::endl;
+        return transformer;
+    }
+
+    if (transformer) transformer->setObjectName(transform.getIdentifier());
+    return transformer;
+}
+
+Model *
+ModelTransformerFactory::transform(const Transform &transform,
+                                   const ModelTransformer::Input &input,
+                                   QString &message)
+{
+    ModelTransformer *t = createTransformer(transform, input);
+    if (!t) return 0;
+
+    connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
+
+    m_runningTransformers.insert(t);
+
+    t->start();
+    Model *model = t->detachOutputModel();
+
+    if (model) {
+        QString imn = input.getModel()->objectName();
+        QString trn =
+            TransformFactory::getInstance()->getTransformFriendlyName
+            (transform.getIdentifier());
+        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();
+    }
+
+    message = t->getMessage();
+
+    return model;
+}
+
+void
+ModelTransformerFactory::transformerFinished()
+{
+    QObject *s = sender();
+    ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
+    
+    std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
+
+    if (!transformer) {
+	std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
+	return;
+    }
+
+    if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
+        std::cerr << "WARNING: ModelTransformerFactory::transformerFinished(" 
+                  << transformer
+                  << "): I have no record of this transformer running!"
+                  << std::endl;
+    }
+
+    m_runningTransformers.erase(transformer);
+
+    transformer->wait(); // unnecessary but reassuring
+    delete transformer;
+}
+
+void
+ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
+{
+    TransformerSet affected;
+
+    for (TransformerSet::iterator i = m_runningTransformers.begin();
+         i != m_runningTransformers.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        if (t->getInputModel() == m || t->getOutputModel() == m) {
+            affected.insert(t);
+        }
+    }
+
+    for (TransformerSet::iterator i = affected.begin();
+         i != affected.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        t->abandon();
+
+        t->wait(); // this should eventually call back on
+                   // transformerFinished, which will remove from
+                   // m_runningTransformers and delete.
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/ModelTransformerFactory.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,96 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _MODEL_TRANSFORMER_FACTORY_H_
+#define _MODEL_TRANSFORMER_FACTORY_H_
+
+#include "Transform.h"
+#include "TransformDescription.h"
+
+#include "ModelTransformer.h"
+
+#include <map>
+#include <set>
+
+namespace Vamp { class PluginBase; }
+
+class AudioCallbackPlaySource;
+
+class ModelTransformerFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~ModelTransformerFactory();
+
+    static ModelTransformerFactory *getInstance();
+
+    /**
+     * Fill out the configuration for the given transform (by asking
+     * the user, most likely).  Returns the selected input model and
+     * channel if the transform is acceptable, or an input with a null
+     * model if the operation should be cancelled.  Audio callback
+     * play source may be used to audition effects plugins, if
+     * provided.
+     */
+    ModelTransformer::Input
+    getConfigurationForTransform(Transform &transform,
+                                 const std::vector<Model *> &candidateInputModels,
+                                 Model *defaultInputModel,
+                                 AudioCallbackPlaySource *source = 0,
+                                 size_t startFrame = 0,
+                                 size_t duration = 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.  Set message if there is any error or
+     * warning to report.
+     * 
+     * The returned model is owned by the caller and must be deleted
+     * when no longer needed.
+     */
+    Model *transform(const Transform &transform,
+                     const ModelTransformer::Input &input,
+                     QString &message);
+
+protected slots:
+    void transformerFinished();
+
+    void modelAboutToBeDeleted(Model *);
+
+protected:
+    ModelTransformer *createTransformer(const Transform &transform,
+                                        const ModelTransformer::Input &input);
+
+    typedef std::map<TransformId, QString> TransformerConfigurationMap;
+    TransformerConfigurationMap m_lastConfigurations;
+
+    typedef std::set<ModelTransformer *> TransformerSet;
+    TransformerSet m_runningTransformers;
+
+    bool getChannelRange(TransformId identifier,
+                         Vamp::PluginBase *plugin, int &min, int &max);
+
+    static ModelTransformerFactory *m_instance;
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/RealTimeEffectModelTransformer.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,279 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "RealTimeEffectModelTransformer.h"
+
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/RealTimePluginInstance.h"
+#include "plugin/PluginXml.h"
+
+#include "data/model/Model.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/DenseTimeValueModel.h"
+#include "data/model/WritableWaveFileModel.h"
+#include "data/model/WaveFileModel.h"
+
+#include "TransformFactory.h"
+
+#include <iostream>
+
+RealTimeEffectModelTransformer::RealTimeEffectModelTransformer(Input in,
+                                                               const Transform &transform) :
+    ModelTransformer(in, transform),
+    m_plugin(0)
+{
+    m_units = TransformFactory::getInstance()->getTransformUnits
+        (transform.getIdentifier());
+    m_outputNo =
+        (transform.getOutput() == "A") ? -1 : transform.getOutput().toInt();
+
+    QString pluginId = transform.getPluginIdentifier();
+
+    if (!m_transform.getBlockSize()) m_transform.setBlockSize(1024);
+
+//    std::cerr << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
+
+    RealTimePluginFactory *factory =
+	RealTimePluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+	std::cerr << "RealTimeEffectModelTransformer: No factory available for plugin id \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    DenseTimeValueModel *input = getConformingInput();
+    if (!input) return;
+
+    m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
+                                          input->getSampleRate(),
+                                          m_transform.getBlockSize(),
+                                          input->getChannelCount());
+
+    if (!m_plugin) {
+	std::cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    TransformFactory::getInstance()->setPluginParameters(m_transform, m_plugin);
+
+    if (m_outputNo >= 0 &&
+        m_outputNo >= int(m_plugin->getControlOutputCount())) {
+        std::cerr << "RealTimeEffectModelTransformer: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
+        return;
+    }
+
+    if (m_outputNo == -1) {
+
+        size_t outputChannels = m_plugin->getAudioOutputCount();
+        if (outputChannels > input->getChannelCount()) {
+            outputChannels = input->getChannelCount();
+        }
+
+        WritableWaveFileModel *model = new WritableWaveFileModel
+            (input->getSampleRate(), outputChannels);
+
+        m_output = model;
+
+    } else {
+	
+        SparseTimeValueModel *model = new SparseTimeValueModel
+            (input->getSampleRate(), m_transform.getBlockSize(), 0.0, 0.0, false);
+
+        if (m_units != "") model->setScaleUnits(m_units);
+
+        m_output = model;
+    }
+}
+
+RealTimeEffectModelTransformer::~RealTimeEffectModelTransformer()
+{
+    delete m_plugin;
+}
+
+DenseTimeValueModel *
+RealTimeEffectModelTransformer::getConformingInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "RealTimeEffectModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+RealTimeEffectModelTransformer::run()
+{
+    DenseTimeValueModel *input = getConformingInput();
+    if (!input) return;
+
+    while (!input->isReady()) {
+        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
+        std::cerr << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
+    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
+    if (!stvm && !wwfm) return;
+
+    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
+
+    size_t sampleRate = input->getSampleRate();
+    size_t channelCount = input->getChannelCount();
+    if (!wwfm && m_input.getChannel() != -1) channelCount = 1;
+
+    long blockSize = m_plugin->getBufferSize();
+
+    float **inbufs = m_plugin->getAudioInputBuffers();
+
+    long startFrame = m_input.getModel()->getStartFrame();
+    long   endFrame = m_input.getModel()->getEndFrame();
+    
+    RealTime contextStartRT = m_transform.getStartTime();
+    RealTime contextDurationRT = m_transform.getDuration();
+
+    long contextStart =
+        RealTime::realTime2Frame(contextStartRT, sampleRate);
+
+    long contextDuration =
+        RealTime::realTime2Frame(contextDurationRT, sampleRate);
+
+    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_input.getChannel(), 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 {
+            got = input->getData(0, channelCount - 1,
+                                 blockFrame, blockSize,
+                                 inbufs);
+            while (got < blockSize) {
+                for (size_t ch = 0; ch < channelCount; ++ch) {
+                    inbufs[ch][got] = 0.0;
+                }
+                ++got;
+	    }
+            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/transform/RealTimeEffectModelTransformer.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,43 @@
+/* -*- 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 "ModelTransformer.h"
+#include "plugin/RealTimePluginInstance.h"
+
+class DenseTimeValueModel;
+
+class RealTimeEffectModelTransformer : public ModelTransformer
+{
+public:
+    RealTimeEffectModelTransformer(Input input,
+                                   const Transform &transform);
+    virtual ~RealTimeEffectModelTransformer();
+
+protected:
+    virtual void run();
+
+    QString m_units;
+    RealTimePluginInstance *m_plugin;
+    int m_outputNo;
+
+    // just casts
+    DenseTimeValueModel *getConformingInput();
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/Transform.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,426 @@
+/* -*- 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-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
+    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 "Transform.h"
+
+#include "plugin/PluginIdentifier.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+
+#include <QXmlAttributes>
+
+#include <QDomDocument>
+#include <QDomElement>
+#include <QDomNamedNodeMap>
+#include <QDomAttr>
+
+#include <QTextStream>
+
+#include <iostream>
+
+Transform::Transform() :
+    m_stepSize(0),
+    m_blockSize(0),
+    m_windowType(HanningWindow),
+    m_sampleRate(0)
+{
+}
+
+Transform::Transform(QString xml) :
+    m_stepSize(0),
+    m_blockSize(0),
+    m_windowType(HanningWindow),
+    m_sampleRate(0)
+{
+    QDomDocument doc;
+    
+    QString error;
+    int errorLine;
+    int errorColumn;
+
+    if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
+        std::cerr << "Transform::Transform: Error in parsing XML: "
+                  << error.toStdString() << " at line " << errorLine
+                  << ", column " << errorColumn << std::endl;
+        std::cerr << "Input follows:" << std::endl;
+        std::cerr << xml.toStdString() << std::endl;
+        std::cerr << "Input ends." << std::endl;
+        return;
+    }
+    
+    QDomElement transformElt = doc.firstChildElement("transform");
+    QDomNamedNodeMap attrNodes = transformElt.attributes();
+    QXmlAttributes attrs;
+
+    for (unsigned int i = 0; i < attrNodes.length(); ++i) {
+        QDomAttr attr = attrNodes.item(i).toAttr();
+        if (!attr.isNull()) attrs.append(attr.name(), "", "", attr.value());
+    }
+
+    setFromXmlAttributes(attrs);
+
+    for (QDomElement paramElt = transformElt.firstChildElement("parameter");
+         !paramElt.isNull();
+         paramElt = paramElt.nextSiblingElement("parameter")) {
+
+        QDomNamedNodeMap paramAttrs = paramElt.attributes();
+
+        QDomAttr nameAttr = paramAttrs.namedItem("name").toAttr();
+        if (nameAttr.isNull() || nameAttr.value() == "") continue;
+        
+        QDomAttr valueAttr = paramAttrs.namedItem("value").toAttr();
+        if (valueAttr.isNull() || valueAttr.value() == "") continue;
+
+        setParameter(nameAttr.value(), valueAttr.value().toFloat());
+    }
+
+    for (QDomElement configElt = transformElt.firstChildElement("configuration");
+         !configElt.isNull();
+         configElt = configElt.nextSiblingElement("configuration")) {
+
+        QDomNamedNodeMap configAttrs = configElt.attributes();
+
+        QDomAttr nameAttr = configAttrs.namedItem("name").toAttr();
+        if (nameAttr.isNull() || nameAttr.value() == "") continue;
+        
+        QDomAttr valueAttr = configAttrs.namedItem("value").toAttr();
+        if (valueAttr.isNull() || valueAttr.value() == "") continue;
+
+        setConfigurationValue(nameAttr.value(), valueAttr.value());
+    }
+}
+
+Transform::~Transform()
+{
+}
+
+bool
+Transform::operator==(const Transform &t)
+{
+    return 
+        m_id == t.m_id &&
+        m_parameters == t.m_parameters &&
+        m_configuration == t.m_configuration &&
+        m_program == t.m_program &&
+        m_stepSize == t.m_stepSize &&
+        m_blockSize == t.m_blockSize &&
+        m_windowType == t.m_windowType &&
+        m_startTime == t.m_startTime &&
+        m_duration == t.m_duration &&
+        m_sampleRate == t.m_sampleRate;
+}
+
+void
+Transform::setIdentifier(TransformId id)
+{
+    m_id = id;
+}
+
+TransformId
+Transform::getIdentifier() const
+{
+    return m_id;
+}
+
+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
+{
+    if (FeatureExtractionPluginFactory::instanceFor(getPluginIdentifier())) {
+        return FeatureExtraction;
+    } else {
+        // We don't have an unknown/invalid return value, so always
+        // return this
+        return RealTimeEffect;
+    }
+}
+
+QString
+Transform::getPluginIdentifier() const
+{
+    return m_id.section(':', 0, 2);
+}
+
+QString
+Transform::getOutput() const
+{
+    return m_id.section(':', 3);
+}
+
+void
+Transform::setPluginIdentifier(QString pluginIdentifier)
+{
+    m_id = pluginIdentifier + ':' + getOutput();
+}
+
+void
+Transform::setOutput(QString output)
+{
+    m_id = getPluginIdentifier() + ':' + output;
+}
+
+TransformId
+Transform::getIdentifierForPluginOutput(QString pluginIdentifier,
+                                        QString output)
+{
+    return pluginIdentifier + ':' + output;
+}
+
+const Transform::ParameterMap &
+Transform::getParameters() const
+{
+    return m_parameters;
+}
+
+void
+Transform::setParameters(const ParameterMap &pm)
+{
+    m_parameters = pm;
+}
+
+void
+Transform::setParameter(QString name, float value)
+{
+    std::cerr << "Transform::setParameter(" << name.toStdString()
+              << ") -> " << value << std::endl;
+    m_parameters[name] = value;
+}
+
+const Transform::ConfigurationMap &
+Transform::getConfiguration() const
+{
+    return m_configuration;
+}
+
+void
+Transform::setConfiguration(const ConfigurationMap &cm)
+{
+    m_configuration = cm;
+}
+
+void
+Transform::setConfigurationValue(QString name, QString value)
+{
+    std::cerr << "Transform::setConfigurationValue(" << name.toStdString()
+              << ") -> " << value.toStdString() << std::endl;
+    m_configuration[name] = value;
+}
+
+QString
+Transform::getPluginVersion() const
+{
+    return m_pluginVersion;
+}
+
+void
+Transform::setPluginVersion(QString version)
+{
+    m_pluginVersion = version;
+}
+
+QString
+Transform::getProgram() const
+{
+    return m_program;
+}
+
+void
+Transform::setProgram(QString program)
+{
+    m_program = program;
+}
+
+    
+size_t
+Transform::getStepSize() const
+{
+    return m_stepSize;
+}
+
+void
+Transform::setStepSize(size_t s)
+{
+    m_stepSize = s;
+}
+    
+size_t
+Transform::getBlockSize() const
+{
+    return m_blockSize;
+}
+
+void
+Transform::setBlockSize(size_t s)
+{
+    m_blockSize = s;
+}
+
+WindowType
+Transform::getWindowType() const
+{
+    return m_windowType;
+}
+
+void
+Transform::setWindowType(WindowType type)
+{
+    m_windowType = type;
+}
+
+RealTime
+Transform::getStartTime() const
+{
+    return m_startTime;
+}
+
+void
+Transform::setStartTime(RealTime t)
+{
+    m_startTime = t;
+}
+
+RealTime
+Transform::getDuration() const
+{
+    return m_duration;
+}
+
+void
+Transform::setDuration(RealTime d)
+{
+    m_duration = d;
+}
+    
+float
+Transform::getSampleRate() const
+{
+    return m_sampleRate;
+}
+
+void
+Transform::setSampleRate(float rate)
+{
+    m_sampleRate = rate;
+}
+
+void
+Transform::toXml(QTextStream &out, QString indent, QString extraAttributes) const
+{
+    out << indent;
+
+    bool haveContent = true;
+    if (m_parameters.empty() && m_configuration.empty()) haveContent = false;
+
+    out << QString("<transform id=\"%1\" pluginVersion=\"%2\" program=\"%3\" stepSize=\"%4\" blockSize=\"%5\" windowType=\"%6\" startTime=\"%7\" duration=\"%8\" sampleRate=\"%9\"")
+        .arg(encodeEntities(m_id))
+        .arg(encodeEntities(m_pluginVersion))
+        .arg(encodeEntities(m_program))
+        .arg(m_stepSize)
+        .arg(m_blockSize)
+        .arg(encodeEntities(Window<float>::getNameForType(m_windowType).c_str()))
+        .arg(encodeEntities(m_startTime.toString().c_str()))
+        .arg(encodeEntities(m_duration.toString().c_str()))
+        .arg(m_sampleRate);
+
+    if (extraAttributes != "") {
+        out << " " << extraAttributes;
+    }
+
+    if (haveContent) {
+
+        out << ">\n";
+
+        for (ParameterMap::const_iterator i = m_parameters.begin();
+             i != m_parameters.end(); ++i) {
+            out << indent << "  "
+                << QString("<parameter name=\"%1\" value=\"%2\"/>\n")
+                .arg(encodeEntities(i->first))
+                .arg(i->second);
+        }
+        
+        for (ConfigurationMap::const_iterator i = m_configuration.begin();
+             i != m_configuration.end(); ++i) {
+            out << indent << "  "
+                << QString("<configuration name=\"%1\" value=\"%2\"/>\n")
+                .arg(encodeEntities(i->first))
+                .arg(encodeEntities(i->second));
+        }
+
+        out << indent << "</transform>\n";
+
+    } else {
+
+        out << "/>\n";
+    }
+}
+
+void
+Transform::setFromXmlAttributes(const QXmlAttributes &attrs)
+{
+    if (attrs.value("id") != "") {
+        setIdentifier(attrs.value("id"));
+    }
+
+    if (attrs.value("pluginVersion") != "") {
+        setPluginVersion(attrs.value("pluginVersion"));
+    }
+
+    if (attrs.value("program") != "") {
+        setProgram(attrs.value("program"));
+    }
+
+    if (attrs.value("stepSize") != "") {
+        setStepSize(attrs.value("stepSize").toInt());
+    }
+
+    if (attrs.value("blockSize") != "") {
+        setBlockSize(attrs.value("blockSize").toInt());
+    }
+
+    if (attrs.value("windowType") != "") {
+        setWindowType(Window<float>::getTypeForName
+                      (attrs.value("windowType").toStdString()));
+    }
+
+    if (attrs.value("startTime") != "") {
+        setStartTime(RealTime::fromString(attrs.value("startTime").toStdString()));
+    }
+
+    if (attrs.value("duration") != "") {
+        setStartTime(RealTime::fromString(attrs.value("duration").toStdString()));
+    }
+    
+    if (attrs.value("sampleRate") != "") {
+        setSampleRate(attrs.value("sampleRate").toFloat());
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/Transform.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,155 @@
+/* -*- 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-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
+    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/XmlExportable.h"
+#include "base/Window.h"
+#include "base/RealTime.h"
+
+#include <QString>
+
+typedef QString TransformId;
+
+class QXmlAttributes;
+
+namespace Vamp {
+    class PluginBase;
+}
+
+class Transform : public XmlExportable
+{
+public:
+    /**
+     * Construct a new Transform with default data and no identifier.
+     * The Transform object will be meaningless until some data and an
+     * identifier have been set on it.
+     *
+     * To construct a Transform for use with a particular transform
+     * identifier, use TransformFactory::getDefaultTransformFor.
+     */
+    Transform();
+
+    /**
+     * Construct a Transform by parsing the given XML data string.
+     * This is the inverse of toXml.
+     */
+    Transform(QString xml);
+
+    virtual ~Transform();
+
+    /**
+     * Compare two Transforms.  They only compare equal if every data
+     * element matches.
+     */
+    bool operator==(const Transform &);
+
+    void setIdentifier(TransformId id);
+    TransformId getIdentifier() const;
+
+    enum Type { FeatureExtraction, RealTimeEffect };
+
+    Type getType() const;
+    QString getPluginIdentifier() const;
+    QString getOutput() const;
+    
+    void setPluginIdentifier(QString pluginIdentifier);
+    void setOutput(QString output);
+
+    // Turn a plugin ID and output name into a transform ID.  Note
+    // that our pluginIdentifier is the same thing as the Vamp SDK's
+    // PluginLoader::PluginKey.
+    static TransformId getIdentifierForPluginOutput(QString pluginIdentifier,
+                                                    QString output = "");
+
+    typedef std::map<QString, float> ParameterMap;
+    
+    const ParameterMap &getParameters() const;
+    void setParameters(const ParameterMap &pm);
+    void setParameter(QString name, float value);
+
+    typedef std::map<QString, QString> ConfigurationMap;
+
+    const ConfigurationMap &getConfiguration() const;
+    void setConfiguration(const ConfigurationMap &cm);
+    void setConfigurationValue(QString name, QString value);
+
+    QString getPluginVersion() const;
+    void setPluginVersion(QString version);
+
+    QString getProgram() const;
+    void setProgram(QString program);
+    
+    size_t getStepSize() const;
+    void setStepSize(size_t s);
+    
+    size_t getBlockSize() const;
+    void setBlockSize(size_t s);
+    
+    WindowType getWindowType() const;
+    void setWindowType(WindowType type);
+    
+    RealTime getStartTime() const;
+    void setStartTime(RealTime t);
+    
+    RealTime getDuration() const; // 0 -> all
+    void setDuration(RealTime d);
+    
+    float getSampleRate() const; // 0 -> as input
+    void setSampleRate(float rate);
+
+    void toXml(QTextStream &stream, QString indent = "",
+               QString extraAttributes = "") const;
+
+    /**
+     * Set the main transform data from the given XML attributes.
+     * This does not set the parameters or configuration, which are
+     * exported to separate XML elements rather than attributes of the
+     * transform element.
+     * 
+     * Note that this only sets those attributes which are actually
+     * present in the argument.  Any attributes not defined in the
+     * attribute will remain unchanged in the Transform.  If your aim
+     * is to create a transform exactly matching the given attributes,
+     * ensure you start from an empty transform rather than one that
+     * has already been configured.
+     */
+    void setFromXmlAttributes(const QXmlAttributes &);
+
+protected:
+    TransformId m_id; // pluginid:output, that is type:soname:label:output
+    
+    static QString createIdentifier
+    (QString type, QString soName, QString label, QString output);
+
+    static void parseIdentifier
+    (QString identifier,
+     QString &type, QString &soName, QString &label, QString &output);
+
+    ParameterMap m_parameters;
+    ConfigurationMap m_configuration;
+    QString m_pluginVersion;
+    QString m_program;
+    size_t m_stepSize;
+    size_t m_blockSize;
+    WindowType m_windowType;
+    RealTime m_startTime;
+    RealTime m_duration;
+    float m_sampleRate;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/TransformDescription.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,77 @@
+/* -*- 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-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
+    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_DESCRIPTION_H_
+#define _TRANSFORM_DESCRIPTION_H_
+
+#include "Transform.h"
+
+#include <QString>
+
+#include <vector>
+
+/**
+ * Metadata associated with a transform.
+ *
+ * The transform ID is the same as that used in the Transform class.
+ * It 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 that creates these objects
+ * should 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.
+ *
+ * To obtain these objects, use
+ * TransformFactory::getAllTransformDescriptions and
+ * TransformFactory::getTransformDescription.
+ */
+
+struct TransformDescription
+{
+    TransformDescription() { }
+    TransformDescription(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 TransformDescription &od) const {
+        return
+            (name <  od.name) ||
+            (name == od.name && identifier < od.identifier);
+    };
+};
+
+typedef std::vector<TransformDescription> TransformList;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/TransformFactory.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,776 @@
+/* -*- 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 "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/RealTimePluginInstance.h"
+#include "plugin/PluginXml.h"
+
+#include "vamp-sdk/Plugin.h"
+#include "vamp-sdk/PluginHostAdapter.h"
+#include "vamp-sdk/hostext/PluginWrapper.h"
+
+#include <iostream>
+#include <set>
+
+#include <QRegExp>
+#include <QTextStream>
+
+TransformFactory *
+TransformFactory::m_instance = new TransformFactory;
+
+TransformFactory *
+TransformFactory::getInstance()
+{
+    return m_instance;
+}
+
+TransformFactory::~TransformFactory()
+{
+}
+
+TransformList
+TransformFactory::getAllTransformDescriptions()
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<TransformDescription> dset;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+//        std::cerr << "inserting transform into set: id = " << i->second.identifier.toStdString() << std::endl;
+	dset.insert(i->second);
+    }
+
+    TransformList list;
+    for (std::set<TransformDescription>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+//        std::cerr << "inserting transform into list: id = " << i->identifier.toStdString() << std::endl;
+	list.push_back(*i);
+    }
+
+    return list;
+}
+
+TransformDescription
+TransformFactory::getTransformDescription(TransformId id)
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    if (m_transforms.find(id) == m_transforms.end()) {
+        return TransformDescription();
+    }
+
+    return m_transforms[id];
+}
+
+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) {
+
+        TransformDescription desc = i->second;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString pn = desc.identifier.section(":", 1, 1);
+
+        if (pluginSources.find(tn) != pluginSources.end()) {
+            if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) {
+                ++names[tn];
+            }
+        } else {
+            ++names[tn];
+            pluginSources[tn] = pn;
+            pluginMakers[tn] = desc.maker;
+        }
+    }
+
+    std::map<QString, int> counts;
+    m_transforms.clear();
+
+    for (TransformDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformDescription desc = i->second;
+	QString identifier = desc.identifier;
+        QString maker = desc.maker;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString to = td.section(": ", 1);
+
+	if (names[tn] > 1) {
+            maker.replace(QRegExp(tr(" [\\(<].*$")), "");
+	    tn = QString("%1 [%2]").arg(tn).arg(maker);
+	}
+
+        if (to != "") {
+            desc.name = QString("%1: %2").arg(tn).arg(to);
+        } else {
+            desc.name = tn;
+        }
+
+	m_transforms[identifier] = desc;
+    }	    
+}
+
+void
+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, 44100);
+
+	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() << " friendly name: " << friendlyName.toStdString() << std::endl;
+
+	    transforms[transformId] = 
+                TransformDescription(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] = 
+                    TransformDescription(tr("Effects Data"),
+                                         category,
+                                         transformId,
+                                         userName,
+                                         userName,
+                                         description,
+                                         maker,
+                                         units,
+                                         configurable);
+            }
+        }
+
+        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
+
+            if (descriptor->audioOutputPortCount > 0) {
+
+                QString transformId = QString("%1:A").arg(pluginId);
+                QString type = tr("Effects");
+
+                QString description = tr("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] =
+                    TransformDescription(type,
+                                         category,
+                                         transformId,
+                                         pluginName,
+                                         pluginName,
+                                         description,
+                                         maker,
+                                         "",
+                                         configurable);
+            }
+        }
+    }
+}
+
+
+Transform
+TransformFactory::getDefaultTransformFor(TransformId id, size_t rate)
+{
+    Transform t;
+    t.setIdentifier(id);
+    if (rate != 0) t.setSampleRate(rate);
+
+    Vamp::PluginBase *plugin = instantiateDefaultPluginFor(id, rate);
+
+    if (plugin) {
+        t.setPluginVersion(QString("%1").arg(plugin->getPluginVersion()));
+        setParametersFromPlugin(t, plugin);
+        makeContextConsistentWithPlugin(t, plugin);
+        delete plugin;
+    }
+
+    return t;
+}
+
+Vamp::PluginBase *
+TransformFactory::instantiatePluginFor(const Transform &transform)
+{
+    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
+        (transform.getIdentifier(), transform.getSampleRate());
+    if (plugin) {
+        setPluginParameters(transform, plugin);
+    }
+    return plugin;
+}
+
+Vamp::PluginBase *
+TransformFactory::instantiateDefaultPluginFor(TransformId identifier, size_t rate)
+{
+    Transform t;
+    t.setIdentifier(identifier);
+    if (rate == 0) rate = 44100;
+    QString pluginId = t.getPluginIdentifier();
+
+    Vamp::PluginBase *plugin = 0;
+
+    if (t.getType() == Transform::FeatureExtraction) {
+
+        FeatureExtractionPluginFactory *factory = 
+            FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+        plugin = factory->instantiatePlugin(pluginId, rate);
+
+    } else {
+
+        RealTimePluginFactory *factory = 
+            RealTimePluginFactory::instanceFor(pluginId);
+            
+        plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1);
+    }
+
+    return plugin;
+}
+
+Vamp::Plugin *
+TransformFactory::downcastVampPlugin(Vamp::PluginBase *plugin)
+{
+    Vamp::Plugin *vp = dynamic_cast<Vamp::Plugin *>(plugin);
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl;
+        vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin); //!!! why?
+}
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl;
+        vp = dynamic_cast<Vamp::HostExt::PluginWrapper *>(plugin); //!!! no, I mean really why?
+    }
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::HostExt::PluginWrapper" << std::endl;
+    }
+    return vp;
+}
+
+bool
+TransformFactory::haveTransform(TransformId identifier)
+{
+    if (m_transforms.empty()) populateTransforms();
+    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 "";
+}
+
+Vamp::Plugin::InputDomain
+TransformFactory::getTransformInputDomain(TransformId identifier)
+{
+    Transform transform;
+    transform.setIdentifier(identifier);
+
+    if (transform.getType() != Transform::FeatureExtraction) {
+        return Vamp::Plugin::TimeDomain;
+    }
+
+    Vamp::Plugin *plugin =
+        downcastVampPlugin(instantiateDefaultPluginFor(identifier, 0));
+
+    if (plugin) {
+        Vamp::Plugin::InputDomain d = plugin->getInputDomain();
+        delete plugin;
+        return d;
+    }
+
+    return Vamp::Plugin::TimeDomain;
+}
+
+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, 44100);
+        if (!plugin) return false;
+
+        min = plugin->getMinChannelCount();
+        max = plugin->getMaxChannelCount();
+        delete plugin;
+
+        return true;
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        // don't need to instantiate
+
+        const RealTimePluginDescriptor *descriptor = 
+            RealTimePluginFactory::instanceFor(id)->
+            getPluginDescriptor(id);
+        if (!descriptor) return false;
+
+        min = descriptor->audioInputPortCount;
+        max = descriptor->audioInputPortCount;
+
+        return true;
+    }
+
+    return false;
+}
+
+void
+TransformFactory::setParametersFromPlugin(Transform &transform,
+                                          Vamp::PluginBase *plugin)
+{
+    Transform::ParameterMap pmap;
+
+    //!!! record plugin & API version
+
+    //!!! check that this is the right plugin!
+
+    Vamp::PluginBase::ParameterList parameters =
+        plugin->getParameterDescriptors();
+
+    for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin();
+         i != parameters.end(); ++i) {
+        pmap[i->identifier.c_str()] = plugin->getParameter(i->identifier);
+    }
+
+    transform.setParameters(pmap);
+
+    if (plugin->getPrograms().empty()) {
+        transform.setProgram("");
+    } else {
+        transform.setProgram(plugin->getCurrentProgram().c_str());
+    }
+
+    RealTimePluginInstance *rtpi =
+        dynamic_cast<RealTimePluginInstance *>(plugin);
+
+    Transform::ConfigurationMap cmap;
+
+    if (rtpi) {
+
+        RealTimePluginInstance::ConfigurationPairMap configurePairs =
+            rtpi->getConfigurePairs();
+
+        for (RealTimePluginInstance::ConfigurationPairMap::const_iterator i
+                 = configurePairs.begin(); i != configurePairs.end(); ++i) {
+            cmap[i->first.c_str()] = i->second.c_str();
+        }
+    }
+
+    transform.setConfiguration(cmap);
+}
+
+void
+TransformFactory::setPluginParameters(const Transform &transform,
+                                      Vamp::PluginBase *plugin)
+{
+    //!!! check plugin & API version (see e.g. PluginXml::setParameters)
+
+    //!!! check that this is the right plugin!
+
+    RealTimePluginInstance *rtpi =
+        dynamic_cast<RealTimePluginInstance *>(plugin);
+
+    if (rtpi) {
+        const Transform::ConfigurationMap &cmap = transform.getConfiguration();
+        for (Transform::ConfigurationMap::const_iterator i = cmap.begin();
+             i != cmap.end(); ++i) {
+            rtpi->configure(i->first.toStdString(), i->second.toStdString());
+        }
+    }
+
+    if (transform.getProgram() != "") {
+        plugin->selectProgram(transform.getProgram().toStdString());
+    }
+
+    const Transform::ParameterMap &pmap = transform.getParameters();
+
+    Vamp::PluginBase::ParameterList parameters =
+        plugin->getParameterDescriptors();
+
+    for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin();
+         i != parameters.end(); ++i) {
+        QString key = i->identifier.c_str();
+        Transform::ParameterMap::const_iterator pmi = pmap.find(key);
+        if (pmi != pmap.end()) {
+            plugin->setParameter(i->identifier, pmi->second);
+        }
+    }
+}
+
+void
+TransformFactory::makeContextConsistentWithPlugin(Transform &transform,
+                                                  Vamp::PluginBase *plugin)
+{
+    const Vamp::Plugin *vp = downcastVampPlugin(plugin);
+
+    if (!vp) {
+        // time domain input for real-time effects plugin
+        if (!transform.getBlockSize()) {
+            if (!transform.getStepSize()) transform.setStepSize(1024);
+            transform.setBlockSize(transform.getStepSize());
+        } else {
+            transform.setStepSize(transform.getBlockSize());
+        }
+    } else {
+        Vamp::Plugin::InputDomain domain = vp->getInputDomain();
+        if (!transform.getStepSize()) {
+            transform.setStepSize(vp->getPreferredStepSize());
+        }
+        if (!transform.getBlockSize()) {
+            transform.setBlockSize(vp->getPreferredBlockSize());
+        }
+        if (!transform.getBlockSize()) {
+            transform.setBlockSize(1024);
+        }
+        if (!transform.getStepSize()) {
+            if (domain == Vamp::Plugin::FrequencyDomain) {
+//                std::cerr << "frequency domain, step = " << blockSize/2 << std::endl;
+                transform.setStepSize(transform.getBlockSize()/2);
+            } else {
+//                std::cerr << "time domain, step = " << blockSize/2 << std::endl;
+                transform.setStepSize(transform.getBlockSize());
+            }
+        }
+    }
+}
+
+QString
+TransformFactory::getPluginConfigurationXml(const Transform &t)
+{
+    QString xml;
+
+    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
+        (t.getIdentifier(), 0);
+    if (!plugin) {
+        std::cerr << "TransformFactory::getPluginConfigurationXml: "
+                  << "Unable to instantiate plugin for transform \""
+                  << t.getIdentifier().toStdString() << "\"" << std::endl;
+        return xml;
+    }
+
+    setPluginParameters(t, plugin);
+
+    QTextStream out(&xml);
+    PluginXml(plugin).toXml(out);
+    delete plugin;
+
+    return xml;
+}
+
+void
+TransformFactory::setParametersFromPluginConfigurationXml(Transform &t,
+                                                          QString xml)
+{
+    Vamp::PluginBase *plugin = instantiateDefaultPluginFor
+        (t.getIdentifier(), 0);
+    if (!plugin) {
+        std::cerr << "TransformFactory::setParametersFromPluginConfigurationXml: "
+                  << "Unable to instantiate plugin for transform \""
+                  << t.getIdentifier().toStdString() << "\"" << std::endl;
+        return;
+    }
+
+    PluginXml(plugin).setParametersFromXml(xml);
+    setParametersFromPlugin(t, plugin);
+    delete plugin;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/TransformFactory.h	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,174 @@
+/* -*- 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-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
+    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 "TransformDescription.h"
+
+#include <vamp-sdk/Plugin.h>
+
+#include <QObject>
+
+#include <map>
+#include <set>
+
+class TransformFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~TransformFactory();
+
+    static TransformFactory *getInstance();
+
+    TransformList getAllTransformDescriptions();
+    TransformDescription getTransformDescription(TransformId id);
+
+    std::vector<QString> getAllTransformTypes();
+    std::vector<QString> getTransformCategories(QString transformType);
+    std::vector<QString> getTransformMakers(QString transformType);
+
+    /**
+     * Return true if the given transform is known.
+     */
+    bool haveTransform(TransformId identifier);
+
+    /**
+     * A single transform ID can lead to many possible Transforms,
+     * with different parameters and execution context settings.
+     * Return the default one for the given transform.
+     */
+    Transform getDefaultTransformFor(TransformId identifier, size_t rate = 0);
+
+    /**
+     * 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);
+
+    Vamp::Plugin::InputDomain getTransformInputDomain(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);
+
+    /**
+     * Load an appropriate plugin for the given transform and set the
+     * parameters, program and configuration strings on that plugin
+     * from the Transform object.
+     *
+     * Note that this requires that the transform has a meaningful
+     * sample rate set, as that is used as the rate for the plugin.  A
+     * Transform can legitimately have rate set at zero (= "use the
+     * rate of the input source"), so the caller will need to test for
+     * this case.
+     *
+     * Returns the plugin thus loaded.  This will be a
+     * Vamp::PluginBase, but not necessarily a Vamp::Plugin (only if
+     * the transform was a feature-extraction type -- call
+     * downcastVampPlugin if you only want Vamp::Plugins).  Returns
+     * NULL if no suitable plugin was available.
+     *
+     * The returned plugin is owned by the caller, and should be
+     * deleted (using "delete") when no longer needed.
+     */
+    Vamp::PluginBase *instantiatePluginFor(const Transform &transform);
+
+    /**
+     * Convert a Vamp::PluginBase to a Vamp::Plugin, if it is one.
+     * Return NULL otherwise.  This ill-fitting convenience function
+     * is really just a dynamic_cast wrapper.
+     */
+    Vamp::Plugin *downcastVampPlugin(Vamp::PluginBase *);
+
+    /**
+     * Set the plugin parameters, program and configuration strings on
+     * the given Transform object from the given plugin instance.
+     * Note that no check is made whether the plugin is actually the
+     * "correct" one for the transform.
+     */
+    void setParametersFromPlugin(Transform &transform, Vamp::PluginBase *plugin);
+
+    /**
+     * Set the parameters, program and configuration strings on the
+     * given plugin from the given Transform object.
+     */
+    void setPluginParameters(const Transform &transform, Vamp::PluginBase *plugin);
+    
+    /**
+     * If the given Transform object has no processing step and block
+     * sizes set, set them to appropriate defaults for the given
+     * plugin.
+     */
+    void makeContextConsistentWithPlugin(Transform &transform, Vamp::PluginBase *plugin); 
+
+    /**
+     * Retrieve a <plugin ... /> XML fragment that describes the
+     * plugin parameters, program and configuration data for the given
+     * transform.
+     *
+     * This function is provided for backward compatibility only.  Use
+     * Transform::toXml where compatibility with PluginXml
+     * descriptions of transforms is not required.
+     */
+    QString getPluginConfigurationXml(const Transform &transform);
+
+    /**
+     * Set the plugin parameters, program and configuration strings on
+     * the given Transform object from the given <plugin ... /> XML
+     * fragment.
+     *
+     * This function is provided for backward compatibility only.  Use
+     * Transform(QString) where compatibility with PluginXml
+     * descriptions of transforms is not required.
+     */
+    void setParametersFromPluginConfigurationXml(Transform &transform,
+                                                 QString xml);
+
+protected:
+    typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
+    TransformDescriptionMap m_transforms;
+
+    void populateTransforms();
+    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
+    void populateRealTimePlugins(TransformDescriptionMap &);
+
+    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, size_t rate);
+
+    static TransformFactory *m_instance;
+};
+
+
+#endif