changeset 56:2157fa46c1e7

* Add plugin parameter dialog, and use it to set up parameters for feature extraction plugins via a semi-opaque (translucent?) configurationXml string which is associated with a given transform instance. This is not yet saved to and restored from the SV file properly. * Remove no-longer-relevant BeatDetect and BeatDetectionFunction transforms (replaced a long time ago with the beat detector plugin).
author Chris Cannam
date Wed, 22 Mar 2006 17:38:29 +0000
parents 6befca60ab4e
children 7439f1696314
files plugin/PluginInstance.cpp plugin/PluginInstance.h transform/BeatDetectTransform.cpp transform/BeatDetectTransform.h transform/BeatDetectionFunctionTransform.cpp transform/BeatDetectionFunctionTransform.h transform/FeatureExtractionPluginTransform.cpp transform/FeatureExtractionPluginTransform.h transform/TransformFactory.cpp transform/TransformFactory.h
diffstat 10 files changed, 166 insertions(+), 503 deletions(-) [+]
line wrap: on
line diff
--- a/plugin/PluginInstance.cpp	Wed Mar 22 13:23:50 2006 +0000
+++ b/plugin/PluginInstance.cpp	Wed Mar 22 17:38:29 2006 +0000
@@ -18,6 +18,11 @@
 #include <QRegExp>
 #include <QXmlAttributes>
 
+#include <QDomDocument>
+#include <QDomElement>
+#include <QDomNamedNodeMap>
+#include <QDomAttr>
+
 #include <iostream>
 
 QString
@@ -83,17 +88,59 @@
 
     for (ParameterList::const_iterator i = parameters.begin();
          i != parameters.end(); ++i) {
-        QString name = stripInvalidParameterNameCharacters
-            (QString(i->name.c_str()));
+        QString name = QString("param-%1")
+            .arg(stripInvalidParameterNameCharacters
+                 (QString(i->name.c_str())));
         bool ok;
         float value = attrs.value(name).trimmed().toFloat(&ok);
         if (ok) {
             setParameter(i->name, value);
         } else {
-            std::cerr << "WARNING: PluginInstance::setParameters: Invalid value \"" << attrs.value(name).toStdString() << "\" for parameter \"" << i->name << "\"" << std::endl;
+            std::cerr << "WARNING: PluginInstance::setParameters: Invalid value \"" << attrs.value(name).toStdString() << "\" for parameter \"" << i->name << "\" (attribute \"" << name.toStdString() << "\")" << std::endl;
         }
     }
 }
+
+void
+PluginInstance::setParametersFromXml(QString xml)
+{
+    QDomDocument doc;
+
+    QString error;
+    int errorLine;
+    int errorColumn;
+
+    if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
+        std::cerr << "PluginInstance::setParametersFromXml: 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 pluginElt = doc.firstChildElement("plugin");
+
+    if (pluginElt.isNull()) {
+        std::cerr << "pluginElt is null" << std::endl;
+        pluginElt = doc.documentElement();
+        if (pluginElt.isNull()) {
+            std::cerr << "pluginElt is still null" << std::endl;
+        }
+    }
+
+    QDomNamedNodeMap attrNodes = pluginElt.attributes();
+    QXmlAttributes attrs;
+
+    for (int i = 0; i < attrNodes.length(); ++i) {
+        QDomAttr attr = attrNodes.item(i).toAttr();
+        if (attr.isNull()) continue;
+        std::cerr << "Adding attribute \"" << attr.name().toStdString()
+                  << "\" with value \"" << attr.value().toStdString() << "\"" << std::endl;
+        attrs.append(attr.name(), "", "", attr.value());
+    }
+
+    setParameters(attrs);
+}
     
 QString
 PluginInstance::stripInvalidParameterNameCharacters(QString s) const
--- a/plugin/PluginInstance.h	Wed Mar 22 13:23:50 2006 +0000
+++ b/plugin/PluginInstance.h	Wed Mar 22 17:38:29 2006 +0000
@@ -171,6 +171,13 @@
      */
     virtual void setParameters(const QXmlAttributes &);
 
+    /**
+     * Set the parameters and program of a plugin from an XML plugin
+     * element as returned by toXmlString.  This is a partial inverse
+     * of toXmlString.
+     */
+    virtual void setParametersFromXml(QString xml);
+
 protected:
     QString stripInvalidParameterNameCharacters(QString) const;
 };
--- a/transform/BeatDetectTransform.cpp	Wed Mar 22 13:23:50 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +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 "BeatDetectTransform.h"
-
-#include "model/DenseTimeValueModel.h"
-#include "model/SparseOneDimensionalModel.h"
-
-#include <iostream>
-#include "dsp/onsets/DetectionFunction.h"
-#include "dsp/tempotracking/TempoTrack.h"
-
-
-BeatDetectTransform::BeatDetectTransform(Model *inputModel) :
-    Transform(inputModel)
-{
-    // Step resolution for the detection function in seconds
-    double stepSecs = 0.01161;
-
-    // Step resolution for the detection function in samples
-    size_t stepSize = (size_t)floor((double)inputModel->getSampleRate() * 
-				    stepSecs); 
-
-
-//    m_w->m_bdf->setResolution(stepSize);
-//    output->setResolution(stepSize);
-
-    std::cerr << "BeatDetectTransform::BeatDetectTransform: input sample rate " << inputModel->getSampleRate() << ", stepSecs " << stepSecs << ", stepSize " << stepSize << ", unrounded stepSize " << double(inputModel->getSampleRate()) * stepSecs << ", output sample rate " << inputModel->getSampleRate() / stepSize << ", unrounded output sample rate " << double(inputModel->getSampleRate()) / double(stepSize) << std::endl;
-
-    m_output = new SparseOneDimensionalModel(inputModel->getSampleRate(), 1);
-}
-
-BeatDetectTransform::~BeatDetectTransform()
-{
-    // parent does it all
-}
-
-TransformName
-BeatDetectTransform::getName()
-{
-    return tr("Beats");
-}
-
-void
-BeatDetectTransform::run()
-{
-    SparseOneDimensionalModel *output = getOutput();
-    DenseTimeValueModel *input = getInput();
-    if (!input) return;
-
-    DFConfig config;
-
-    config.DFType = DF_COMPLEXSD;
-
-    // Step resolution for the detection function in seconds
-    config.stepSecs = 0.01161;
-
-    // Step resolution for the detection function in samples
-    config.stepSize = (unsigned int)floor((double)input->getSampleRate() * 
-					  config.stepSecs ); 
-
-    config.frameLength = 2 * config.stepSize;
-
-    unsigned int stepSize = config.stepSize;
-    unsigned int frameLength = config.frameLength;
-
-//    m_w->m_bdf->setResolution(stepSize);
-    output->setResolution(stepSize);
-
-    //Tempo Tracking Configuration Parameters
-    TTParams ttparams;
-    
-    // Low Pass filter coefficients for detection function smoothing
-    double* aCoeffs = new double[3];
-    double* bCoeffs = new double[3];
-	
-    aCoeffs[ 0 ] = 1;
-    aCoeffs[ 1 ] = -0.5949;
-    aCoeffs[ 2 ] = 0.2348;
-    bCoeffs[ 0 ] = 0.1600;
-    bCoeffs[ 1 ] = 0.3200;
-    bCoeffs[ 2 ] = 0.1600;
-
-    ttparams.winLength = 512;
-    ttparams.lagLength = 128;
-    ttparams.LPOrd = 2;
-    ttparams.LPACoeffs = aCoeffs;
-    ttparams.LPBCoeffs = bCoeffs; 
-    ttparams.alpha = 9;
-    ttparams.WinT.post = 8;
-    ttparams.WinT.pre = 7;
-
-    ////////////////////////////////////////////////////////////
-    // DetectionFunction
-    ////////////////////////////////////////////////////////////
-    // Instantiate and configure detection function object
-
-    DetectionFunction df(config);
-
-    size_t origin = input->getStartFrame();
-    size_t frameCount = input->getEndFrame() - origin;
-    size_t blocks = (frameCount / stepSize);
-    if (blocks * stepSize < frameCount) ++blocks;
-
-    double *buffer = new double[frameLength];
-
-    // DF output with causal extension
-    unsigned int clen = blocks + ttparams.winLength;
-    double *dfOutput = new double[clen];
-
-    std::cerr << "Detecting beats at step size " << stepSize << "..." << std::endl;
-
-    for (size_t i = 0; i < clen; ++i) {
-
-//	std::cerr << "block " << i << "/" << clen << std::endl;
-//	std::cerr << ".";
-
-	if (i < blocks) {
-	    size_t got = input->getValues(-1, //!!! needs to come from parent layer -- which is not supposed to be in scope at this point
-					  origin + i * stepSize,
-					  origin + i * stepSize + frameLength,
-					  buffer);
-	    while (got < frameLength) buffer[got++] = 0.0;
-	    dfOutput[i] = df.process(buffer);
-	} else {
-	    dfOutput[i] = 0.0;
-	}
-
-//	m_w->m_bdf->addPoint(SparseTimeValueModel::Point
-//			     (i * stepSize, dfOutput[i],
-//			      QString("%1").arg(dfOutput[i])));
-//	m_w->m_bdf->setCompletion(i * 99 / clen);
-	output->setCompletion(i * 99 / clen);
-
-	if (m_deleting) {
-	    delete [] buffer;
-	    delete [] dfOutput;
-	    delete [] aCoeffs;
-	    delete [] bCoeffs;
-	    return;
-	}
-    }
-
-//    m_w->m_bdf->setCompletion(100);
-
-    // Tempo Track Object instantiation and configuration
-    TempoTrack tempoTracker(ttparams);
-
-    // Vector of detected onsets
-    vector<int> beats; 
-
-    std::cerr << "Running tempo tracker..." << std::endl;
-
-    beats = tempoTracker.process(dfOutput, blocks);
-
-    delete [] buffer;
-    delete [] dfOutput;
-    delete [] aCoeffs;
-    delete [] bCoeffs;
-
-    for (size_t i = 0; i < beats.size(); ++i) {
-//	std::cerr << "Beat value " << beats[i] << ", multiplying out to " << beats[i] * stepSize << std::endl;
-	float bpm = 0.0;
-	int fdiff = 0;
-	if (i < beats.size() - 1) {
-	    fdiff = (beats[i+1] - beats[i]) * stepSize;
-	    // one beat is fdiff frames, so there are samplerate/fdiff bps,
-	    // so 60*samplerate/fdiff bpm
-	    if (fdiff > 0) {
-		bpm = (60.0 * input->getSampleRate()) / fdiff;
-	    }
-	}
-	output->addPoint(SparseOneDimensionalModel::Point
-			 (origin + beats[i] * stepSize, QString("%1").arg(bpm)));
-	if (m_deleting) return;
-    }
-
-    output->setCompletion(100);
-}
-
-DenseTimeValueModel *
-BeatDetectTransform::getInput()
-{
-    DenseTimeValueModel *dtvm =
-	dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-	std::cerr << "BeatDetectTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-    }
-    return dtvm;
-}
-
-SparseOneDimensionalModel *
-BeatDetectTransform::getOutput()
-{
-    return static_cast<SparseOneDimensionalModel *>(getOutputModel());
-}
-
--- a/transform/BeatDetectTransform.h	Wed Mar 22 13:23:50 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +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 _BEAT_DETECT_TRANSFORM_H_
-#define _BEAT_DETECT_TRANSFORM_H_
-
-#include "Transform.h"
-
-//!!! This should be replaced by a plugin, when we have a plugin
-// transform.  But it's easier to start by testing concrete examples.
-
-class DenseTimeValueModel;
-class SparseOneDimensionalModel;
-
-class BeatDetectTransform : public Transform
-{
-public:
-    BeatDetectTransform(Model *inputModel);
-    virtual ~BeatDetectTransform();
-
-    static TransformName getName();
-
-protected:
-    virtual void run();
-
-    // just casts
-    DenseTimeValueModel *getInput();
-    SparseOneDimensionalModel *getOutput();
-};
-
-#endif
-
--- a/transform/BeatDetectionFunctionTransform.cpp	Wed Mar 22 13:23:50 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-   
-    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 "BeatDetectionFunctionTransform.h"
-
-#include "model/DenseTimeValueModel.h"
-#include "model/SparseTimeValueModel.h"
-
-#include <iostream>
-#include "dsp/onsets/DetectionFunction.h"
-#include "dsp/tempotracking/TempoTrack.h"
-
-
-BeatDetectionFunctionTransform::BeatDetectionFunctionTransform(Model *inputModel) :
-    Transform(inputModel)
-{
-    m_output = new SparseTimeValueModel(inputModel->getSampleRate(), 1,
-					0.0, 0.0, false);
-}
-
-BeatDetectionFunctionTransform::~BeatDetectionFunctionTransform()
-{
-    // parent does it all
-}
-
-TransformName
-BeatDetectionFunctionTransform::getName()
-{
-    return tr("Beat Detection Function");
-}
-
-void
-BeatDetectionFunctionTransform::run()
-{
-    SparseTimeValueModel *output = getOutput();
-    DenseTimeValueModel *input = getInput();
-    if (!input) {
-	std::cerr << "BeatDetectionFunctionTransform::run: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
-	return;
-    }
-
-    DFConfig config;
-
-    config.DFType = DF_COMPLEXSD;
-
-    // Step resolution for the detection function in seconds
-    config.stepSecs = 0.01161;
-
-    // Step resolution for the detection function in samples
-    config.stepSize = (unsigned int)floor((double)input->getSampleRate() * 
-					  config.stepSecs ); 
-
-    config.frameLength = 2 * config.stepSize;
-
-    unsigned int stepSize = config.stepSize;
-    unsigned int frameLength = config.frameLength;
-
-    output->setResolution(stepSize);
-
-    //Tempo Tracking Configuration Parameters
-    TTParams ttparams;
-    
-    // Low Pass filter coefficients for detection function smoothing
-    double* aCoeffs = new double[3];
-    double* bCoeffs = new double[3];
-	
-    aCoeffs[ 0 ] = 1;
-    aCoeffs[ 1 ] = -0.5949;
-    aCoeffs[ 2 ] = 0.2348;
-    bCoeffs[ 0 ] = 0.1600;
-    bCoeffs[ 1 ] = 0.3200;
-    bCoeffs[ 2 ] = 0.1600;
-
-    ttparams.winLength = 512;
-    ttparams.lagLength = 128;
-    ttparams.LPOrd = 2;
-    ttparams.LPACoeffs = aCoeffs;
-    ttparams.LPBCoeffs = bCoeffs; 
-    ttparams.alpha = 9;
-    ttparams.WinT.post = 8;
-    ttparams.WinT.pre = 7;
-
-    ////////////////////////////////////////////////////////////
-    // DetectionFunction
-    ////////////////////////////////////////////////////////////
-    // Instantiate and configure detection function object
-
-    DetectionFunction df(config);
-
-    size_t origin = input->getStartFrame();
-    size_t frameCount = input->getEndFrame() - origin;
-    size_t blocks = (frameCount / stepSize);
-    if (blocks * stepSize < frameCount) ++blocks;
-
-    double *buffer = new double[frameLength];
-
-    // DF output with causal extension
-    unsigned int clen = blocks + ttparams.winLength;
-    double *dfOutput = new double[clen];
-
-    std::cerr << "Running beat detection function at step size " << stepSize << "..." << std::endl;
-
-    for (size_t i = 0; i < clen; ++i) {
-
-//	std::cerr << "block " << i << "/" << clen << std::endl;
-//	std::cerr << ".";
-
-	if (i < blocks) {
-	    size_t got = input->getValues(-1, //!!! needs to come from parent layer -- which is not supposed to be in scope at this point
-					  origin + i * stepSize,
-					  origin + i * stepSize + frameLength,
-					  buffer);
-	    while (got < frameLength) buffer[got++] = 0.0;
-	    dfOutput[i] = df.process(buffer);
-	} else {
-	    dfOutput[i] = 0.0;
-	}
-
-	output->addPoint(SparseTimeValueModel::Point
-			 (i * stepSize, dfOutput[i],
-			  QString("%1").arg(dfOutput[i])));
-//	m_w->m_bdf->setCompletion(i * 99 / clen);
-	output->setCompletion(i * 99 / clen);
-
-	if (m_deleting) {
-	    delete [] buffer;
-	    delete [] dfOutput;
-	    delete [] aCoeffs;
-	    delete [] bCoeffs;
-	    return;
-	}
-    }
-
-    output->setCompletion(100);
-}
-
-DenseTimeValueModel *
-BeatDetectionFunctionTransform::getInput()
-{
-    return dynamic_cast<DenseTimeValueModel *>(getInputModel());
-}
-
-SparseTimeValueModel *
-BeatDetectionFunctionTransform::getOutput()
-{
-    return static_cast<SparseTimeValueModel *>(getOutputModel());
-}
-
--- a/transform/BeatDetectionFunctionTransform.h	Wed Mar 22 13:23:50 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +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 _BEAT_DETECTION_FUNCTION_TRANSFORM_H_
-#define _BEAT_DETECTION_FUNCTION_TRANSFORM_H_
-
-#include "Transform.h"
-
-//!!! This should be replaced by a plugin, when we have a plugin
-// transform.  But it's easier to start by testing concrete examples.
-
-class DenseTimeValueModel;
-class SparseTimeValueModel;
-
-class BeatDetectionFunctionTransform : public Transform
-{
-public:
-    BeatDetectionFunctionTransform(Model *inputModel);
-    virtual ~BeatDetectionFunctionTransform();
-
-    static TransformName getName();
-
-protected:
-    virtual void run();
-
-    // just casts
-    DenseTimeValueModel *getInput();
-    SparseTimeValueModel *getOutput();
-};
-
-#endif
-
--- a/transform/FeatureExtractionPluginTransform.cpp	Wed Mar 22 13:23:50 2006 +0000
+++ b/transform/FeatureExtractionPluginTransform.cpp	Wed Mar 22 17:38:29 2006 +0000
@@ -29,6 +29,7 @@
 
 FeatureExtractionPluginTransform::FeatureExtractionPluginTransform(Model *inputModel,
 								   QString pluginId,
+                                                                   QString configurationXml,
 								   QString outputName) :
     Transform(inputModel),
     m_plugin(0),
@@ -54,6 +55,10 @@
 	return;
     }
 
+    if (configurationXml != "") {
+        m_plugin->setParametersFromXml(configurationXml);
+    }
+
     FeatureExtractionPlugin::OutputList outputs =
 	m_plugin->getOutputDescriptors();
 
--- a/transform/FeatureExtractionPluginTransform.h	Wed Mar 22 13:23:50 2006 +0000
+++ b/transform/FeatureExtractionPluginTransform.h	Wed Mar 22 17:38:29 2006 +0000
@@ -26,6 +26,7 @@
 public:
     FeatureExtractionPluginTransform(Model *inputModel,
 				     QString plugin,
+                                     QString configurationXml = "",
 				     QString outputName = "");
     virtual ~FeatureExtractionPluginTransform();
 
--- a/transform/TransformFactory.cpp	Wed Mar 22 13:23:50 2006 +0000
+++ b/transform/TransformFactory.cpp	Wed Mar 22 17:38:29 2006 +0000
@@ -15,12 +15,12 @@
 
 #include "TransformFactory.h"
 
-#include "BeatDetectTransform.h"
-#include "BeatDetectionFunctionTransform.h"
 #include "FeatureExtractionPluginTransform.h"
 
 #include "plugin/FeatureExtractionPluginFactory.h"
 
+#include "widgets/PluginParameterDialog.h"
+
 #include <iostream>
 
 TransformFactory *
@@ -42,9 +42,9 @@
     if (m_transforms.empty()) populateTransforms();
 
     TransformList list;
-    for (TransformMap::const_iterator i = m_transforms.begin();
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
 	 i != m_transforms.end(); ++i) {
-	list.push_back(TransformDesc(i->first, i->second));
+	list.push_back(i->second);
     }
 
     return list;
@@ -97,7 +97,13 @@
 		    .arg(outputs[j].description.c_str());
 	    }
 
-	    m_transforms[transformName] = userDescription;
+            bool configurable = (!plugin->getPrograms().empty() ||
+                                 !plugin->getParameterDescriptors().empty());
+
+	    m_transforms[transformName] = 
+                TransformDesc(transformName,
+                              userDescription,
+                              configurable);
 	    
 	    makers[transformName] = plugin->getMaker().c_str();
 	}
@@ -107,22 +113,23 @@
 
     std::map<QString, int> descriptions;
 
-    for (TransformMap::iterator i = m_transforms.begin(); i != m_transforms.end();
-	 ++i) {
+    for (TransformDescriptionMap::iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
 
-	QString name = i->first, description = i->second;
+        TransformDesc desc = i->second;
 
-	++descriptions[description];
-	++descriptions[QString("%1 [%2]").arg(description).arg(makers[name])];
+	++descriptions[desc.description];
+	++descriptions[QString("%1 [%2]").arg(desc.description).arg(makers[desc.name])];
     }
 
     std::map<QString, int> counts;
-    TransformMap newMap;
+    TransformDescriptionMap newMap;
 
-    for (TransformMap::iterator i = m_transforms.begin(); i != m_transforms.end();
-	 ++i) {
+    for (TransformDescriptionMap::iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
 
-	QString name = i->first, description = i->second;
+        TransformDesc desc = i->second;
+	QString name = desc.name, description = desc.description;
 
 	if (descriptions[description] > 1) {
 	    description = QString("%1 [%2]").arg(description).arg(makers[name]);
@@ -132,7 +139,8 @@
 	    }
 	}
 
-	newMap[name] = description;
+        desc.description = description;
+	newMap[name] = desc;
     }	    
 	    
     m_transforms = newMap;
@@ -142,7 +150,7 @@
 TransformFactory::getTransformDescription(TransformName name)
 {
     if (m_transforms.find(name) != m_transforms.end()) {
-	return m_transforms[name];
+	return m_transforms[name].description;
     } else return "";
 }
 
@@ -159,32 +167,63 @@
     }
 }
 
-Transform *
-TransformFactory::createTransform(TransformName name, Model *inputModel)
+bool
+TransformFactory::getConfigurationForTransform(TransformName name,
+                                               Model *inputModel,
+                                               QString &configurationXml)
 {
-    return createTransform(name, inputModel, true);
+    QString id = name.section(':', 0, 2);
+    QString output = name.section(':', 3);
+    
+    bool ok = false;
+    configurationXml = m_lastConfigurations[name];
+
+    std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+        FeatureExtractionPlugin *plugin =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel->getSampleRate());
+        if (plugin) {
+            if (configurationXml != "") {
+                plugin->setParametersFromXml(configurationXml);
+            }
+            PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
+            if (dialog->exec() == QDialog::Accepted) {
+                ok = true;
+            }
+            configurationXml = plugin->toXmlString();
+            delete plugin;
+        }
+    }
+
+    if (ok) m_lastConfigurations[name] = configurationXml;
+
+    return ok;
 }
 
 Transform *
 TransformFactory::createTransform(TransformName name, Model *inputModel,
-				  bool start)
+				  QString configurationXml, bool start)
 {
     Transform *transform = 0;
 
-    if (name == BeatDetectTransform::getName()) {
-	transform = new BeatDetectTransform(inputModel);
-    } else if (name == BeatDetectionFunctionTransform::getName()) {
-	transform = new BeatDetectionFunctionTransform(inputModel);
+    // The only transform type we support at the moment is the
+    // FeatureExtractionPluginTransform.  In future we may wish to
+    // support e.g. RealTimePluginTransform for audio->audio or
+    // audio->midi transforms using standard effects plugins.
+
+    QString id = name.section(':', 0, 2);
+    QString output = name.section(':', 3);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+        transform = new FeatureExtractionPluginTransform(inputModel,
+                                                         id,
+                                                         configurationXml,
+                                                         output);
     } else {
-	QString id = name.section(':', 0, 2);
-	QString output = name.section(':', 3);
-	if (FeatureExtractionPluginFactory::instanceFor(id)) {
-	    transform = new FeatureExtractionPluginTransform(inputModel,
-							     id, output);
-	} else {
-	    std::cerr << "TransformFactory::createTransform: Unknown transform "
-		      << name.toStdString() << std::endl;
-	}
+        std::cerr << "TransformFactory::createTransform: Unknown transform "
+                  << name.toStdString() << std::endl;
     }
 
     if (start && transform) transform->start();
@@ -193,9 +232,10 @@
 }
 
 Model *
-TransformFactory::transform(TransformName name, Model *inputModel)
+TransformFactory::transform(TransformName name, Model *inputModel,
+                            QString configurationXml)
 {
-    Transform *t = createTransform(name, inputModel, false);
+    Transform *t = createTransform(name, inputModel, configurationXml, false);
 
     if (!t) return 0;
 
--- a/transform/TransformFactory.h	Wed Mar 22 13:23:50 2006 +0000
+++ b/transform/TransformFactory.h	Wed Mar 22 17:38:29 2006 +0000
@@ -29,23 +29,33 @@
 
     static TransformFactory *instance();
 
-    // The name is intended to be computer-referencable, and unique
+    // The name is intended to be computer-referenceable, and unique
     // within the application.  The description is intended to be
     // human readable.  In principle it doesn't have to be unique, but
     // the factory will add suffixes to ensure that it is, all the
     // same (just to avoid user confusion).
 
     struct TransformDesc {
-	TransformDesc(TransformName _name, QString _description = "") :
-	    name(_name), description(_description) { }
+        TransformDesc() { }
+	TransformDesc(TransformName _name, QString _description, bool _configurable) :
+	    name(_name), description(_description), configurable(_configurable) { }
 	TransformName name;
 	QString description;
+        bool configurable;
     };
     typedef std::vector<TransformDesc> TransformList;
 
     TransformList getAllTransforms();
 
     /**
+     * Get a configuration XML string for the given transform (by
+     * asking the user, most likely).  Returns true if the transform
+     * is acceptable, false if the operation should be cancelled.
+     */
+    bool getConfigurationForTransform(TransformName name, Model *inputModel,
+                                      QString &configurationXml);
+
+    /**
      * 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
@@ -58,7 +68,8 @@
      * The returned model is owned by the caller and must be deleted
      * when no longer needed.
      */
-    Model *transform(TransformName name, Model *inputModel);
+    Model *transform(TransformName name, Model *inputModel,
+                     QString configurationXml = "");
 
     /**
      * Full description of a transform, suitable for putting on a menu.
@@ -82,12 +93,21 @@
     void transformFinished();
 
 protected:
-    Transform *createTransform(TransformName name, Model *inputModel);
     Transform *createTransform(TransformName name, Model *inputModel,
-			       bool start);
+                               QString configurationXml, bool start);
 
-    typedef std::map<TransformName, QString> TransformMap;
-    TransformMap m_transforms;
+    struct TransformIdent
+    {
+        TransformName name;
+        QString configurationXml;
+    };
+
+    typedef std::map<TransformName, QString> TransformConfigurationMap;
+    TransformConfigurationMap m_lastConfigurations;
+
+    typedef std::map<TransformName, TransformDesc> TransformDescriptionMap;
+    TransformDescriptionMap m_transforms;
+
     void populateTransforms();
 
     static TransformFactory *m_instance;