changeset 320:32e50b620a6c

* Move some things around to facilitate plundering libraries for other applications without needing to duplicate so much code. sv/osc -> data/osc sv/audioio -> audioio sv/transform -> plugin/transform sv/document -> document (will rename to framework in next commit)
author Chris Cannam
date Wed, 24 Oct 2007 16:34:31 +0000
parents 3ff8f571da09
children 1dc99b430d2a
files data/data.pro data/osc/OSCMessage.cpp data/osc/OSCMessage.h data/osc/OSCQueue.cpp data/osc/OSCQueue.h data/osc/demoscript.sh data/osc/sv-command data/osc/sv-osc-send.c plugin/plugin.pro plugin/transform/FeatureExtractionPluginTransform.cpp plugin/transform/FeatureExtractionPluginTransform.h plugin/transform/PluginTransform.cpp plugin/transform/PluginTransform.h plugin/transform/RealTimePluginTransform.cpp plugin/transform/RealTimePluginTransform.h plugin/transform/Transform.cpp plugin/transform/Transform.h plugin/transform/TransformFactory.cpp plugin/transform/TransformFactory.h
diffstat 19 files changed, 3355 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/data/data.pro	Wed Oct 24 15:21:38 2007 +0000
+++ b/data/data.pro	Wed Oct 24 16:34:31 2007 +0000
@@ -1,6 +1,6 @@
 TEMPLATE = lib
 
-SV_UNIT_PACKAGES = fftw3f sndfile mad quicktime id3tag oggz fishsound
+SV_UNIT_PACKAGES = fftw3f sndfile mad quicktime id3tag oggz fishsound liblo
 load(../sv.prf)
 
 CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
@@ -8,8 +8,8 @@
 
 TARGET = svdata
 
-DEPENDPATH += fft fileio model ..
-INCLUDEPATH += . fft model fileio ..
+DEPENDPATH += fft fileio model osc ..
+INCLUDEPATH += . fft fileio model osc ..
 OBJECTS_DIR = tmp_obj
 MOC_DIR = tmp_moc
 
@@ -61,7 +61,9 @@
            model/SparseValueModel.h \
            model/TextModel.h \
            model/WaveFileModel.h \
-           model/WritableWaveFileModel.h
+           model/WritableWaveFileModel.h \
+           osc/OSCMessage.h \
+           osc/OSCQueue.h 
 SOURCES += fft/FFTapi.cpp \
            fft/FFTDataServer.cpp \
            fft/FFTFileCache.cpp \
@@ -98,4 +100,6 @@
            model/PowerOfTwoZoomConstraint.cpp \
            model/RangeSummarisableTimeValueModel.cpp \
            model/WaveFileModel.cpp \
-           model/WritableWaveFileModel.cpp
+           model/WritableWaveFileModel.cpp \
+           osc/OSCMessage.cpp \
+           osc/OSCQueue.cpp 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/OSCMessage.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,52 @@
+/* -*- 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 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.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Chris Cannam.
+*/
+
+#include "OSCMessage.h"
+
+
+OSCMessage::~OSCMessage()
+{
+    clearArgs();
+}
+
+void
+OSCMessage::clearArgs()
+{
+    m_args.clear();
+}
+
+void
+OSCMessage::addArg(QVariant arg)
+{
+    m_args.push_back(arg);
+}
+
+size_t
+OSCMessage::getArgCount() const
+{
+    return m_args.size();
+}
+
+const QVariant &
+OSCMessage::getArg(size_t i) const
+{
+    return m_args[i];
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/OSCMessage.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,58 @@
+/* -*- 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 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.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Chris Cannam.
+*/
+
+#ifndef _OSC_MESSAGE_H_
+#define _OSC_MESSAGE_H_
+
+#include <QString>
+#include <QVariant>
+
+#include <vector>
+#include <map>
+
+class OSCMessage
+{
+public:
+    OSCMessage() { }
+    ~OSCMessage();
+
+    void setTarget(const int &target) { m_target = target; }
+    int getTarget() const { return m_target; }
+
+    void setTargetData(const int &targetData) { m_targetData = targetData; }
+    int getTargetData() const { return m_targetData; }
+
+    void setMethod(QString method) { m_method = method; }
+    QString getMethod() const { return m_method; }
+
+    void clearArgs();
+    void addArg(QVariant arg);
+
+    size_t getArgCount() const;
+    const QVariant &getArg(size_t i) const;
+
+private:
+    int m_target;
+    int m_targetData;
+    QString m_method;
+    std::vector<QVariant> m_args;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/OSCQueue.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,222 @@
+/* -*- 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 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.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Chris Cannam and QMUL.
+*/
+
+#include "OSCQueue.h"
+
+#include <iostream>
+
+#define OSC_MESSAGE_QUEUE_SIZE 1023
+
+#ifdef HAVE_LIBLO
+
+void
+OSCQueue::oscError(int num, const char *msg, const char *path)
+{
+    std::cerr << "ERROR: OSCQueue::oscError: liblo server error " << num
+	      << " in path " << path << ": " << msg << std::endl;
+}
+
+int
+OSCQueue::oscMessageHandler(const char *path, const char *types, lo_arg **argv,
+                            int argc, lo_message, void *user_data)
+{
+    OSCQueue *queue = static_cast<OSCQueue *>(user_data);
+
+    int target;
+    int targetData;
+    QString method;
+
+    if (!queue->parseOSCPath(path, target, targetData, method)) {
+	return 1;
+    }
+
+    OSCMessage message;
+    message.setTarget(target);
+    message.setTargetData(targetData);
+    message.setMethod(method);
+
+    int i = 0;
+
+    while (types && i < argc && types[i]) {
+
+        char type = types[i];
+        lo_arg *arg = argv[i];
+
+        switch (type) {
+        case 'i': message.addArg(arg->i); break;
+            // This conversion fails to compile in 64-bit environments
+            // at present, and we don't use the h type anyway so we
+            // can safely omit it
+//        case 'h': message.addArg(arg->h); break;
+        case 'f': message.addArg(arg->f); break;
+        case 'd': message.addArg(arg->d); break;
+        case 'c': message.addArg(arg->c); break;
+        case 't': message.addArg(arg->i); break;
+        case 's': message.addArg(&arg->s); break;
+        default:  std::cerr << "WARNING: OSCQueue::oscMessageHandler: "
+                            << "Unsupported OSC type '" << type << "'" 
+                            << std::endl;
+            break;
+        }
+
+	++i;
+    }
+
+    queue->postMessage(message);
+    return 0;
+}
+
+#endif
+   
+OSCQueue::OSCQueue() :
+#ifdef HAVE_LIBLO
+    m_thread(0),
+#endif
+    m_buffer(OSC_MESSAGE_QUEUE_SIZE)
+{
+#ifdef HAVE_LIBLO
+    m_thread = lo_server_thread_new(NULL, oscError);
+
+    lo_server_thread_add_method(m_thread, NULL, NULL,
+                                oscMessageHandler, this);
+
+    lo_server_thread_start(m_thread);
+
+    std::cout << "OSCQueue::OSCQueue: Base OSC URL is "
+              << lo_server_thread_get_url(m_thread) << std::endl;
+#endif
+}
+
+OSCQueue::~OSCQueue()
+{
+#ifdef HAVE_LIBLO
+    if (m_thread) {
+        lo_server_thread_stop(m_thread);
+    }
+#endif
+
+    while (m_buffer.getReadSpace() > 0) {
+        delete m_buffer.readOne();
+    }
+}
+
+bool
+OSCQueue::isOK() const
+{
+#ifdef HAVE_LIBLO
+    return (m_thread != 0);
+#else
+    return false;
+#endif
+}
+
+QString
+OSCQueue::getOSCURL() const
+{
+    QString url = "";
+#ifdef HAVE_LIBLO
+    url = lo_server_thread_get_url(m_thread);
+#endif
+    return url;
+}
+
+size_t
+OSCQueue::getMessagesAvailable() const
+{
+    return m_buffer.getReadSpace();
+}
+
+OSCMessage
+OSCQueue::readMessage()
+{
+    OSCMessage *message = m_buffer.readOne();
+    OSCMessage rmessage = *message;
+    delete message;
+    return rmessage;
+}
+
+void
+OSCQueue::postMessage(OSCMessage message)
+{
+    int count = 0, max = 5;
+    while (m_buffer.getWriteSpace() == 0) {
+        if (count == max) {
+            std::cerr << "ERROR: OSCQueue::postMessage: OSC message queue is full and not clearing -- abandoning incoming message" << std::endl;
+            return;
+        }
+        std::cerr << "WARNING: OSCQueue::postMessage: OSC message queue (capacity " << m_buffer.getSize() << " is full!" << std::endl;
+        std::cerr << "Waiting for something to be processed" << std::endl;
+#ifdef _WIN32
+        Sleep(1);
+#else
+        sleep(1);
+#endif
+        count++;
+    }
+
+    OSCMessage *mp = new OSCMessage(message);
+    m_buffer.write(&mp, 1);
+    std::cerr << "OSCQueue::postMessage: Posted OSC message: target "
+              << message.getTarget() << ", target data " << message.getTargetData()
+              << ", method " << message.getMethod().toStdString() << std::endl;
+    emit messagesAvailable();
+}
+
+bool
+OSCQueue::parseOSCPath(QString path, int &target, int &targetData,
+                       QString &method)
+{
+    while (path.startsWith("/")) {
+	path = path.right(path.length()-1);
+    }
+
+    int i = 0;
+
+    bool ok = false;
+    target = path.section('/', i, i).toInt(&ok);
+
+    if (!ok) {
+        target = 0;
+    } else {
+        ++i;
+        targetData = path.section('/', i, i).toInt(&ok);
+        if (!ok) {
+            targetData = 0;
+        } else {
+            ++i;
+        }
+    }
+
+    method = path.section('/', i, -1);
+
+    if (method.contains('/')) {
+        std::cerr << "ERROR: OSCQueue::parseOSCPath: malformed path \""
+                  << path.toStdString() << "\" (should be target/data/method or "
+                  << "target/method or method, where target and data "
+                  << "are numeric)" << std::endl;
+        return false;
+    }
+
+    std::cerr << "OSCQueue::parseOSCPath: good path \"" << path.toStdString()
+              << "\"" << std::endl;
+
+    return true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/OSCQueue.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,69 @@
+/* -*- 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 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.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Chris Cannam and QMUL.
+*/
+
+#ifndef _OSC_QUEUE_H_
+#define _OSC_QUEUE_H_
+
+#include "OSCMessage.h"
+
+#include "base/RingBuffer.h"
+
+#include <QObject>
+
+#ifdef HAVE_LIBLO
+#include <lo/lo.h>
+#endif
+
+class OSCQueue : public QObject
+{
+    Q_OBJECT
+
+public:
+    OSCQueue();
+    virtual ~OSCQueue();
+
+    bool isOK() const;
+
+    bool isEmpty() const { return getMessagesAvailable() == 0; }
+    size_t getMessagesAvailable() const;
+    OSCMessage readMessage();
+
+    QString getOSCURL() const;
+
+signals:
+    void messagesAvailable();
+
+protected:
+#ifdef HAVE_LIBLO
+    lo_server_thread m_thread;
+
+    static void oscError(int, const char *, const char *);
+    static int oscMessageHandler(const char *, const char *, lo_arg **,
+                                 int, lo_message, void *);
+#endif
+
+    void postMessage(OSCMessage);
+    bool parseOSCPath(QString path, int &target, int &targetData, QString &method);
+
+    RingBuffer<OSCMessage *> m_buffer;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/demoscript.sh	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,541 @@
+#!/bin/bash
+
+audio=/data/music
+preferred=$audio/free
+list=audiofiles.txt
+used=audiofiles-used.txt
+
+df=vamp:vamp-aubio:aubioonset:detectionfunction
+#df=vamp:qm-vamp-plugins:qm-tempotracker:detection_fn
+onsets=vamp:vamp-aubio:aubioonset:onsets
+#onsets=vamp:qm-vamp-plugins:qm-tempotracker:beats
+beats=vamp:vamp-aubio:aubiotempo:beats
+#beats=$onsets
+#onsets=$beats
+chromagram=vamp:qm-vamp-plugins:qm-chromagram:chromagram
+notes=vamp:vamp-aubio:aubionotes:notes
+
+pid=`cat /tmp/demoscript.pid 2>/dev/null`
+if [ -n "$pid" ]; then
+    kill "$pid"
+fi
+echo $$ > /tmp/demoscript.pid
+trap "rm /tmp/demoscript.pid" 0
+
+sv-command quit
+sleep 1
+killall -9 sonic-visualiser
+sleep 1
+
+pick_file()
+{
+    file=""
+    count=`wc -l "$list" 2>/dev/null | awk '{ print $1 }'`
+    if [ ! -f "$list" ] || [ "$count" -eq "0" ] ; then
+	find "$audio" -name \*.ogg -print >> "$list"
+	find "$audio" -name \*.mp3 -print >> "$list"
+	find "$audio" -name \*.wav -print >> "$list"
+	find "$preferred" -name \*.ogg -print >> "$list"
+	find "$preferred" -name \*.mp3 -print >> "$list"
+	find "$preferred" -name \*.wav -print >> "$list"
+	count=`wc -l "$list" 2>/dev/null | awk '{ print $1 }'`
+    fi
+    while [ -z "$file" ]; do
+	index=$((RANDOM % $count))
+	file=`tail +"$index" "$list" | head -1`
+	[ -f "$file" ] || continue
+    done
+    fgrep -v "$file" "$list" > "$list"_ && mv "$list"_ "$list"
+    echo "$file"
+}
+
+load_a_file()
+{
+    file=`pick_file`
+    if ! sv-command open "$file"; then
+	pid="`pidof sonic-visualiser`"
+	if [ -z "$pid" ]; then
+	    ( setsid sonic-visualiser -geometry 1000x500+10+100 & )
+	    sleep 2
+            sudo renice +19 `pidof sonic-visualiser`
+            sudo renice +18 `pidof Xorg`
+            sv-command resize 1000 500
+	    load_a_file
+	else
+	    echo "ERROR: Unable to contact sonic-visualiser pid $pid" 1>&2
+	    exit 1
+	fi
+    fi
+}
+
+show_stuff()
+{
+    sv-command set overlays 2
+#    sv-command set zoomwheels 1
+    sv-command set propertyboxes 1
+}
+
+hide_stuff()
+{
+    sv-command set overlays 0
+#    sv-command set zoomwheels 0
+    sv-command set propertyboxes 0
+}
+
+reset()
+{
+    for pane in 1 2 3 4 5; do
+	for layer in 1 2 3 4 5 6 7 8 9 10; do
+	    sv-command delete layer
+	done
+	sv-command delete pane
+    done
+    sv-command zoom default
+    sv-command add waveform
+    show_stuff
+}
+
+scroll_and_zoom()
+{
+    sv-command set overlays 0
+    sv-command set zoomwheels 0
+    sv-command set propertyboxes 0
+#    sv-command setcurrent 1 1
+#    sv-command delete layer
+#    sv-command setcurrent 1 1
+    sv-command set layer Colour Red
+    sleep 1
+    sv-command set pane Global-Zoom off
+    sv-command set pane Global-Scroll off
+    sv-command set pane Follow-Playback Scroll
+    for zoom in 950 900 850 800 750 700 650 600 550 512 450 400 350 300 256 192 160 128 96 64 48 32 24 16; do
+	sv-command zoom $zoom
+	sleep 0.1
+    done
+}
+
+play()
+{
+    sv-command play "$@"
+}
+
+fade_in()
+{
+    sv-command set gain 0
+    sleep 0.5
+    play "$@"
+    for gain in 0.001 0.01 0.05 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1; do
+	sv-command set gain $gain
+	sleep 0.1
+    done
+}
+
+fade_out()
+{
+    for gain in 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0.05 0.01 0.001; do
+	sv-command set gain $gain
+	sleep 0.1
+    done
+    stop
+    sv-command set gain 1
+}
+
+slow()
+{
+#    for speed in -1 -10 -20 -30 -40 -50 -60 -70 -80 -100 -140 -200 -250 -300 -400 -500 -700 -800 -900 -1000; do
+#	sv-command set speedup "$speed"
+#	sleep 1
+#    done
+    for speed in -20 -100 -1000; do
+        sv-command set speedup "$speed"
+        sleep 10
+    done
+}
+
+stop()
+{
+    sv-command stop "$@"
+    sv-command set speedup 0
+}
+
+quit()
+{
+    sv-command quit
+}
+
+add_melodic_range_spectrogram()
+{
+    sv-command set propertyboxes 1
+    sv-command add spectrogram
+    sv-command set layer Window-Size 8192
+#    sv-command set layer Window-Size 4096
+    sv-command set layer Window-Overlap 4
+#    sv-command set layer Window-Overlap 3
+    sv-command set layer Frequency-Scale Log
+    sv-command set layer Colour-Scale Meter
+}
+
+zoom_in_spectrogram() 
+{
+    sv-command zoomvertical 43 8000
+    for x in 1 2 3 4 5 6; do
+	max=$((8000 - 1000*$x))
+	sv-command zoomvertical 43 "$max"
+	sleep 0.5
+    done
+    for x in 1 2 3 4 5; do
+	max=$((2000 - 100 * $x))
+	sv-command zoomvertical 43 "$max"
+	sleep 0.5
+    done
+}
+
+zoom_in_spectrogram_further() 
+{
+    for x in 1 2 3 4 5; do
+	sv-command zoomvertical in
+    done
+}
+
+playback_bits()
+{
+    sv-command setcurrent 1
+    sv-command set pane Global-Zoom off
+    sv-command set pane Global-Scroll off
+    sv-command set pane Follow-Playback Scroll
+    sv-command jump 10
+    sv-command setcurrent 1 1
+    sv-command delete layer
+    sv-command setcurrent 1 1
+#    sv-command setcurrent 1 2
+    sv-command set layer Colour Blue
+    sleep 5
+    hide_stuff
+    sv-command set overlays 0
+    sv-command set zoomwheels 0
+    sv-command set propertyboxes 0
+    fade_in
+    sleep 10
+#    sv-command set layer Colour Blue
+#    sleep 1
+#    sv-command set layer Colour Orange
+#    sleep 1
+#    sv-command set layer Colour Red
+#    sleep 1
+#    sv-command set layer Colour Green
+#    sleep 1
+#    sleep 1
+    
+    
+#    scroll_and_zoom
+
+#    sv-command set overlays 0
+#    sv-command set zoomwheels 0
+#    sv-command set propertyboxes 0
+#    sv-command setcurrent 1 1
+#    sv-command delete layer
+#    sv-command setcurrent 1 1
+#    sv-command set layer Colour Red
+#    sleep 1
+#    sv-command set pane Global-Zoom off
+#    sv-command set pane Global-Scroll off
+#    sv-command set pane Follow-Playback Scroll
+    sv-command set zoomwheels 1
+    sleep 1
+    for zoom in 950 900 850 800 750 700 650 600 550 512 450 400 350 300 256 192 160 128 96 64 48 32 24 16; do
+	sv-command zoom $zoom
+	sleep 0.1
+    done
+    
+    sleep 1
+    sv-command set zoomwheels 0
+    sv-command zoom 16
+
+    sleep 10
+    #slow
+    #sv-command set layer Normalize-Visible-Area on
+#    for zoom in 15 14 13 12 11 10 9 8 7 6 5 4 ; do
+#	sv-command zoom $zoom
+#	sleep 0.1
+ #   done
+    sleep 1
+    sv-command set zoomwheels 0
+    slow
+    sleep 7
+    fade_out
+    sv-command setcurrent 1
+    sv-command set pane Follow-Playback Page
+    sv-command set pane Global-Zoom on
+    sv-command set pane Global-Scroll on
+    done_playback_bits=1
+}
+
+spectrogram_bits()
+{
+    sv-command set pane Global-Zoom on
+    sv-command zoom 1024
+    add_melodic_range_spectrogram
+    sv-command zoom 1024
+    sleep 5
+    sv-command jump 10
+    sleep 20
+    zoom_in_spectrogram
+    sleep 20
+
+    sv-command select 7.5 11
+    fade_in selection
+    sleep 10
+    sv-command set speedup -200
+    sleep 10
+    sv-command setcurrent 1
+    sv-command delete pane
+    sv-command zoom in
+    sv-command setcurrent 1 2
+    sv-command set layer Normalize-Columns off
+    sv-command set layer Normalize-Visible-Area on
+    sleep 20
+    sv-command set speedup 0
+    sleep 10
+    sv-command select none
+#    fade_out
+
+#    if [ -n "$done_playback_bits" ]; then
+#	sv-command setcurrent 1
+#	sv-command zoom out
+#	sv-command zoom outvamp:qm-vamp-plugins:qm-chromagram:chromagram
+#	sv-command zoom out
+#	sv-command zoom out
+#	sv-command zoom out
+#	sv-command setcurrent 2
+#    fi
+    
+#    hide_stuff
+#    fade_in
+    sleep 10
+#    sv-command set layer Bin-Display Frequencies
+#    sv-command set layer Normalize-Columns on
+#    sleep 20
+    sv-command set layer Bin-Display "All Bins"
+    sv-command set layer Normalize-Columns on
+    sv-command set layer Normalize-Visible-Area off
+    sv-command set layer Colour-Scale 0
+    sv-command set layer Colour "Red on Blue"
+    sv-command zoomvertical 23 800
+    sleep 20
+    sv-command transform $onsets
+    sv-command set layer Colour Orange
+    sleep 20
+    fade_out
+    sleep 1
+#    sv-command jump 10
+#    sv-command setcurrent 1 2
+#    sv-command set layer Colour "Black on White"
+#    sv-command transform $notes
+#    sv-command set layer Colour Orange
+    sleep 10
+#    sv-command setcurrent 1 3
+#    sv-command delete layer
+    sv-command setcurrent 1 3
+    sv-command delete layer
+    sv-command setcurrent 1 2
+    sv-command set layer Colour Default
+    done_spectrogram_bits=1
+
+#    zoom_in_spectrogram_further
+}
+
+onset_bits()
+{
+    show_stuff
+    sv-command set zoomwheels 0
+    sv-command setcurrent 1
+    sv-command set pane Global-Zoom on
+    sv-command set pane Global-Scroll on
+    sleep 0.5
+    sv-command set layer Colour Blue
+    sleep 0.5
+    sv-command set layer Colour Orange
+    sleep 0.5
+    sv-command set layer Colour Red
+    sleep 0.5
+    sv-command set layer Colour Green
+    sleep 1
+#    sleep 1
+#    if [ -n "$done_spectrogram_bits" ]; then
+#	sv-command setcurrent 2
+#	sv-command delete pane
+#    fi
+#    sv-command zoom default
+#    sv-command zoom in
+#    sv-command zoom in
+#    sv-command zoom in
+    sv-command zoom 192
+    sv-command zoom in
+    sv-command add timeruler
+    sv-command jump 0
+    sv-command transform $df
+    sv-command set layer Colour Black
+    sleep 5
+    sv-command set layer Plot-Type Curve
+    sleep 5
+    sv-command jump 30
+    sv-command setcurrent 1
+    sv-command set pane Follow-Playback Page
+    sv-command transform $df
+    sv-command set layer Colour Red
+    sleep 5
+    sv-command jump 30
+    sleep 5
+    if [ "$RANDOM" -lt 16384 ]; then
+        sv-command set layer Vertical-Scale "Log Scale"
+    fi
+    sv-command set layer Plot-Type Segmentation
+    sleep 5 
+#    hide_stuff
+    sleep 10
+    sv-command set overlays 0
+    sv-command set propertyboxes 0
+#    sv-command setcurrent 1 1
+#    sv-command set layer Colour Black
+#    sv-command setcurrent 1 2
+    sleep 2
+    fade_in
+    sleep 2
+    sv-command transform $onsets
+    sv-command set layer Colour Black
+    sv-command setcurrent 2
+    sv-command transform $onsets
+    sv-command set layer Colour Blue
+    sleep 20
+#    sv-command setcurrent 2
+#    sv-command transform vamp:qm-vamp-plugins:qm-tempotracker:beats
+#    sv-command transform $beats
+    sleep 20
+#    fade_out
+#    show_stuff
+}
+
+selection_bits()
+{
+#    reset
+    sv-command set overlays 1
+    sv-command set zoomwheels 0
+    sv-command resize 1000 500
+    sv-command zoom default
+    sv-command setcurrent 2
+    sv-command delete pane
+#    if [ -n "$done_playback_bits" ]; then
+	sv-command setcurrent 1 2
+#    else
+#	sv-command setcurrent 1 3
+#    fi
+    sv-command delete layer
+#    if [ -n "$done_playback_bits" ]; then
+	sv-command setcurrent 1 2
+#    else
+#	sv-command setcurrent 1 3
+#    fi
+    sv-command delete layer
+    sv-command setcurrent 1 2
+    sv-command set layer Colour Orange
+#    sv-command transform vamp:qm-vamp-plugins:qm-tempotracker:beats
+    sv-command transform $beats
+#    sv-command setcurrent 1 2
+    sv-command set layer Colour Black
+    sleep 20
+    sv-command loop on
+    base=$((RANDOM % 100))
+    sv-command select $base $base.3
+#    fade_in selection
+    play selection
+    sleep 8
+    base=$((base + 4))
+    sv-command addselect $base $base.1
+    #sleep 12
+    base=$((base + 2))
+    sv-command addselect $base $base.1
+    #sleep 6
+    base=$((base + 2))
+    sv-command addselect $base $base.3
+    #sleep 6
+    base=$((base + 3))
+    sv-command addselect $base $base.3
+    #sleep 6
+    base=$((base + 2))
+    sv-command addselect $base $base.3
+    sleep 4
+    sv-command delete layer
+    sleep 16
+    sv-command set speedup -50
+    sleep 14
+    sv-command set speedup 50
+    sleep 8
+    sv-command set speedup 100
+    sleep 5
+    sv-command set speedup 200
+    fade_out
+#    sleep 10
+    sv-command select none
+    sv-command set overlays 2
+    sv-command set propertyboxes 1
+#    sv-command setcurrent 1 3
+#    sv-command delete layer
+    sv-command setcurrent 1 2
+    sv-command set layer Colour Black
+}
+
+chromagram_bits()
+{
+#    add_melodic_range_spectrogram
+#    sleep 10
+    sv-command add timeruler
+    sleep 5
+    sv-command jump 10
+    sv-command zoom out
+    sleep 5
+    sv-command transform $chromagram
+    sleep 40
+    sv-command zoom out
+    fade_in
+    sleep 20
+    fade_out
+}
+
+while /bin/true; do
+
+sleep 2
+load_a_file
+sv-command loop on
+
+sv-command resize 1000 500
+show_stuff
+sleep 5
+sleep 20
+playback_bits
+
+#sleep 10
+sv-command resize 1000 700
+sv-command zoom default
+show_stuff
+onset_bits
+
+selection_bits
+
+#sv-command resize 1000 700
+
+#sleep 10
+sv-command resize 1000 700
+#show_stuff
+spectrogram_bits
+
+#sleep 10
+#sv-command jump 0
+#show_stuff
+#chromagram_bits
+
+sleep 20
+
+#reset
+killall -9 sonic-visualiser
+
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/sv-command	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# A very simple command shell for Sonic Visualiser.
+# 
+# This provides a wrapper for the sv-osc-send program, which is a
+# generic OSC sending program (not specific to SV, despite its name).
+# This script attempts to guess the OSC port number for an SV
+# process running on the local host, and then composes a method name
+# and arguments into a complete OSC call.
+# 
+# You can either run this with the method and its arguments on the
+# command line, e.g. "sv-command set layer Frequency-Scale Log", or
+# you can provide a series of method + argument commands on stdin.
+# 
+# Unless you use the -q option, this script will echo the OSC URL
+# and arguments that it is sending for each command.
+#
+# Note that the method and arguments may not contain spaces.
+# 
+# Chris Cannam, Nov 2006
+
+quiet=
+if [ "$1" = "-q" ]; then
+    quiet=true; shift;
+fi
+
+# The yucky bit
+
+port=`lsof -c sonic- | \
+          grep UDP | \
+          sed -e 's/^.*[^0-9]\([0-9][0-9]*\) *$/\1/' | \
+          grep -v ' ' | \
+          head -1 `
+
+host=127.0.0.1
+scheme=osc.udp
+
+if [ -z "$port" ]; then
+    echo "Sonic Visualiser OSC port not found"
+    exit 1
+fi
+
+if [ -n "$1" ]; then
+    command=$1; shift
+    [ -z "$quiet" ] && echo "$scheme://$host:$port/$command" "$@"
+    sv-osc-send "$scheme://$host:$port/$command" "$@"
+else
+    while read command a1 a2 a3 a4 a5; do
+        [ -z "$command" ] && continue
+	[ -z "$quiet" ] && echo "$scheme://$host:$port/$command" $a1 $a2 $a3 $a4 $a5
+	sv-osc-send "$scheme://$host:$port/$command" $a1 $a2 $a3 $a4 $a5
+    done
+fi
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/osc/sv-osc-send.c	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <lo/lo.h>
+
+void
+usage(char *program_name)
+{
+    char *base_name = strrchr(program_name, '/');
+    
+    if (base_name && *(base_name + 1) != 0) {
+        base_name += 1;
+    } else {
+        base_name = program_name;
+    }
+
+    fprintf(stderr, "\nusage: %s <OSC URL> [<values>]\n\n", program_name);
+    fprintf(stderr, "example OSC URLs:\n\n"
+                    "  osc.udp://localhost:19383/path/test 1.0 4.2\n"
+                    "  osc.udp://my.host.org:10886/3/13/load file\n\n");
+    fprintf(stderr, "numeric arguments will be treated as OSC 'f' floating point types.\n\n");
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    lo_address a;
+    char *url, *host, *port, *path;
+    lo_message message;
+    unsigned int i;
+
+    if (argc < 2) {
+        usage(argv[0]);
+        /* does not return */
+    }
+    url = argv[1];
+
+    host = lo_url_get_hostname(url);
+    port = lo_url_get_port(url);
+    path = lo_url_get_path(url);
+    a = lo_address_new(host, port);
+
+    message = lo_message_new();
+
+    for (i = 0; i + 2 < argc; ++i) {
+
+	int index = i + 2;
+	char *param;
+
+	param = argv[index];
+	if (!isdigit(param[0])) {
+	    lo_message_add_string(message, argv[index]);
+	} else {
+	    lo_message_add_float(message, atof(argv[index]));
+	}
+    }
+
+    lo_send_message(a, path, message);
+
+    if (lo_address_errno(a)) {
+	printf("liblo error: %s\n", lo_address_errstr(a));
+    }
+
+    free(host);
+    free(port);
+    free(path);
+
+    return 0;
+}
+
--- a/plugin/plugin.pro	Wed Oct 24 15:21:38 2007 +0000
+++ b/plugin/plugin.pro	Wed Oct 24 16:34:31 2007 +0000
@@ -11,8 +11,8 @@
 # Doesn't work with this library, which contains C99 as well as C++
 PRECOMPILED_HEADER =
 
-DEPENDPATH += . .. api plugins api/alsa api/alsa/sound
-INCLUDEPATH += . .. api api/alsa plugins api/alsa/sound
+DEPENDPATH += . .. api plugins api/alsa api/alsa/sound transform
+INCLUDEPATH += . .. api api/alsa plugins api/alsa/sound transform
 OBJECTS_DIR = tmp_obj
 MOC_DIR = tmp_moc
 
@@ -34,7 +34,12 @@
            api/alsa/seq.h \
            api/alsa/seq_event.h \
            api/alsa/seq_midi_event.h \
-           api/alsa/sound/asequencer.h
+           api/alsa/sound/asequencer.h \
+           transform/FeatureExtractionPluginTransform.h \
+           transform/PluginTransform.h \
+           transform/RealTimePluginTransform.h \
+           transform/Transform.h \
+           transform/TransformFactory.h
 SOURCES += DSSIPluginFactory.cpp \
            DSSIPluginInstance.cpp \
            FeatureExtractionPluginFactory.cpp \
@@ -45,4 +50,9 @@
            RealTimePluginFactory.cpp \
            RealTimePluginInstance.cpp \
            api/dssi_alsa_compat.c \
-           plugins/SamplePlayer.cpp
+           plugins/SamplePlayer.cpp \
+           transform/FeatureExtractionPluginTransform.cpp \
+           transform/PluginTransform.cpp \
+           transform/RealTimePluginTransform.cpp \
+           transform/Transform.cpp \
+           transform/TransformFactory.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionPluginTransform.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,553 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FeatureExtractionPluginTransform.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/PluginXml.h"
+#include "vamp-sdk/Plugin.h"
+
+#include "data/model/Model.h"
+#include "base/Window.h"
+#include "data/model/SparseOneDimensionalModel.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/EditableDenseThreeDimensionalModel.h"
+#include "data/model/DenseTimeValueModel.h"
+#include "data/model/NoteModel.h"
+#include "data/model/FFTModel.h"
+#include "data/model/WaveFileModel.h"
+
+#include <QMessageBox>
+
+#include <iostream>
+
+FeatureExtractionPluginTransform::FeatureExtractionPluginTransform(Model *inputModel,
+								   QString pluginId,
+                                                                   const ExecutionContext &context,
+                                                                   QString configurationXml,
+								   QString outputName) :
+    PluginTransform(inputModel, context),
+    m_plugin(0),
+    m_descriptor(0),
+    m_outputFeatureNo(0)
+{
+//    std::cerr << "FeatureExtractionPluginTransform::FeatureExtractionPluginTransform: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl;
+
+    FeatureExtractionPluginFactory *factory =
+	FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+	std::cerr << "FeatureExtractionPluginTransform: No factory available for plugin id \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate());
+
+    if (!m_plugin) {
+	std::cerr << "FeatureExtractionPluginTransform: Failed to instantiate plugin \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    if (configurationXml != "") {
+        PluginXml(m_plugin).setParametersFromXml(configurationXml);
+    }
+
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    size_t channelCount = input->getChannelCount();
+    if (m_plugin->getMaxChannelCount() < channelCount) {
+	channelCount = 1;
+    }
+    if (m_plugin->getMinChannelCount() > channelCount) {
+	std::cerr << "FeatureExtractionPluginTransform:: "
+		  << "Can't provide enough channels to plugin (plugin min "
+		  << m_plugin->getMinChannelCount() << ", max "
+		  << m_plugin->getMaxChannelCount() << ", input model has "
+		  << input->getChannelCount() << ")" << std::endl;
+	return;
+    }
+
+    std::cerr << "Initialising feature extraction plugin with channels = "
+              << channelCount << ", step = " << m_context.stepSize
+              << ", block = " << m_context.blockSize << std::endl;
+
+    if (!m_plugin->initialise(channelCount,
+                              m_context.stepSize,
+                              m_context.blockSize)) {
+        std::cerr << "FeatureExtractionPluginTransform: Plugin "
+                  << m_plugin->getIdentifier() << " failed to initialise!" << std::endl;
+        return;
+    }
+
+    Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
+
+    if (outputs.empty()) {
+	std::cerr << "FeatureExtractionPluginTransform: Plugin \""
+		  << pluginId.toStdString() << "\" has no outputs" << std::endl;
+	return;
+    }
+    
+    for (size_t i = 0; i < outputs.size(); ++i) {
+	if (outputName == "" || outputs[i].identifier == outputName.toStdString()) {
+	    m_outputFeatureNo = i;
+	    m_descriptor = new Vamp::Plugin::OutputDescriptor
+		(outputs[i]);
+	    break;
+	}
+    }
+
+    if (!m_descriptor) {
+	std::cerr << "FeatureExtractionPluginTransform: Plugin \""
+		  << pluginId.toStdString() << "\" has no output named \""
+		  << outputName.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+//    std::cerr << "FeatureExtractionPluginTransform: output sample type "
+//	      << m_descriptor->sampleType << std::endl;
+
+    int binCount = 1;
+    float minValue = 0.0, maxValue = 0.0;
+    bool haveExtents = false;
+    
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionPluginTransform: output bin count "
+//	      << binCount << std::endl;
+
+    if (binCount > 0 && m_descriptor->hasKnownExtents) {
+	minValue = m_descriptor->minValue;
+	maxValue = m_descriptor->maxValue;
+        haveExtents = true;
+    }
+
+    size_t modelRate = m_input->getSampleRate();
+    size_t modelResolution = 1;
+    
+    switch (m_descriptor->sampleType) {
+
+    case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
+	if (m_descriptor->sampleRate != 0.0) {
+	    modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001);
+	}
+	break;
+
+    case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
+	modelResolution = m_context.stepSize;
+	break;
+
+    case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
+	modelRate = size_t(m_descriptor->sampleRate + 0.001);
+	break;
+    }
+
+    if (binCount == 0) {
+
+	m_output = new SparseOneDimensionalModel(modelRate, modelResolution,
+						 false);
+
+    } else if (binCount == 1) {
+
+        SparseTimeValueModel *model;
+        if (haveExtents) {
+            model = new SparseTimeValueModel
+                (modelRate, modelResolution, minValue, maxValue, false);
+        } else {
+            model = new SparseTimeValueModel
+                (modelRate, modelResolution, false);
+        }
+        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
+
+        m_output = model;
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+        // We don't have a sparse 3D model, so interpret this as a
+        // note model.  There's nothing to define which values to use
+        // as which parameters of the note -- for the moment let's
+        // treat the first as pitch, second as duration in frames,
+        // third (if present) as velocity. (Our note model doesn't
+        // yet store velocity.)
+        //!!! todo: ask the user!
+	
+        NoteModel *model;
+        if (haveExtents) {
+            model = new NoteModel
+                (modelRate, modelResolution, minValue, maxValue, false);
+        } else {
+            model = new NoteModel
+                (modelRate, modelResolution, false);
+        }            
+        model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
+
+        m_output = model;
+
+    } else {
+
+        EditableDenseThreeDimensionalModel *model =
+            new EditableDenseThreeDimensionalModel
+            (modelRate, modelResolution, binCount, false);
+
+	if (!m_descriptor->binNames.empty()) {
+	    std::vector<QString> names;
+	    for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) {
+		names.push_back(m_descriptor->binNames[i].c_str());
+	    }
+	    model->setBinNames(names);
+	}
+        
+        m_output = model;
+    }
+}
+
+FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform()
+{
+    std::cerr << "FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform()" << std::endl;
+    delete m_plugin;
+    delete m_descriptor;
+}
+
+DenseTimeValueModel *
+FeatureExtractionPluginTransform::getInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "FeatureExtractionPluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+FeatureExtractionPluginTransform::run()
+{
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    if (!m_output) return;
+
+    while (!input->isReady()) {
+/*
+        if (dynamic_cast<WaveFileModel *>(input)) {
+            std::cerr << "FeatureExtractionPluginTransform::run: Model is not ready, but it's not a WaveFileModel (it's a " << typeid(input).name() << "), so that's OK" << std::endl;
+            sleep(2);
+            break; // no need to wait
+        }
+*/
+        std::cerr << "FeatureExtractionPluginTransform::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    size_t sampleRate = m_input->getSampleRate();
+
+    size_t channelCount = input->getChannelCount();
+    if (m_plugin->getMaxChannelCount() < channelCount) {
+	channelCount = 1;
+    }
+
+    float **buffers = new float*[channelCount];
+    for (size_t ch = 0; ch < channelCount; ++ch) {
+	buffers[ch] = new float[m_context.blockSize + 2];
+    }
+
+    bool frequencyDomain = (m_plugin->getInputDomain() ==
+                            Vamp::Plugin::FrequencyDomain);
+    std::vector<FFTModel *> fftModels;
+
+    if (frequencyDomain) {
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            FFTModel *model = new FFTModel
+                                  (getInput(),
+                                   channelCount == 1 ? m_context.channel : ch,
+                                   m_context.windowType,
+                                   m_context.blockSize,
+                                   m_context.stepSize,
+                                   m_context.blockSize,
+                                   false);
+            if (!model->isOK()) {
+                QMessageBox::critical
+                    (0, tr("FFT cache failed"),
+                     tr("Failed to create the FFT model for this transform.\n"
+                        "There may be insufficient memory or disc space to continue."));
+                delete model;
+                setCompletion(100);
+                return;
+            }
+            model->resume();
+            fftModels.push_back(model);
+        }
+    }
+
+    long startFrame = m_input->getStartFrame();
+    long   endFrame = m_input->getEndFrame();
+
+    long contextStart = m_context.startFrame;
+    long contextDuration = m_context.duration;
+
+    if (contextStart == 0 || contextStart < startFrame) {
+        contextStart = startFrame;
+    }
+
+    if (contextDuration == 0) {
+        contextDuration = endFrame - contextStart;
+    }
+    if (contextStart + contextDuration > endFrame) {
+        contextDuration = endFrame - contextStart;
+    }
+
+    long blockFrame = contextStart;
+
+    long prevCompletion = 0;
+
+    setCompletion(0);
+
+    while (!m_abandoned) {
+
+        if (frequencyDomain) {
+            if (blockFrame - int(m_context.blockSize)/2 >
+                contextStart + contextDuration) break;
+        } else {
+            if (blockFrame >= 
+                contextStart + contextDuration) break;
+        }
+
+//	std::cerr << "FeatureExtractionPluginTransform::run: blockFrame "
+//		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
+//                  << m_context.blockSize << std::endl;
+
+	long completion =
+	    (((blockFrame - contextStart) / m_context.stepSize) * 99) /
+	    (contextDuration / m_context.stepSize);
+
+	// channelCount is either m_input->channelCount or 1
+
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            if (frequencyDomain) {
+                int column = (blockFrame - startFrame) / m_context.stepSize;
+                for (size_t i = 0; i <= m_context.blockSize/2; ++i) {
+                    fftModels[ch]->getValuesAt
+                        (column, i, buffers[ch][i*2], buffers[ch][i*2+1]);
+                }
+            } else {
+                getFrames(ch, channelCount, 
+                          blockFrame, m_context.blockSize, buffers[ch]);
+            }                
+        }
+
+	Vamp::Plugin::FeatureSet features = m_plugin->process
+	    (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+
+	for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
+	    Vamp::Plugin::Feature feature =
+		features[m_outputFeatureNo][fi];
+	    addFeature(blockFrame, feature);
+	}
+
+	if (blockFrame == contextStart || completion > prevCompletion) {
+	    setCompletion(completion);
+	    prevCompletion = completion;
+	}
+
+	blockFrame += m_context.stepSize;
+    }
+
+    if (m_abandoned) return;
+
+    Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
+
+    for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) {
+	Vamp::Plugin::Feature feature =
+	    features[m_outputFeatureNo][fi];
+	addFeature(blockFrame, feature);
+    }
+
+    if (frequencyDomain) {
+        for (size_t ch = 0; ch < channelCount; ++ch) {
+            delete fftModels[ch];
+        }
+    }
+
+    setCompletion(100);
+}
+
+void
+FeatureExtractionPluginTransform::getFrames(int channel, int channelCount,
+                                            long startFrame, long size,
+                                            float *buffer)
+{
+    long offset = 0;
+
+    if (startFrame < 0) {
+        for (int i = 0; i < size && startFrame + i < 0; ++i) {
+            buffer[i] = 0.0f;
+        }
+        offset = -startFrame;
+        size -= offset;
+        if (size <= 0) return;
+        startFrame = 0;
+    }
+
+    long got = getInput()->getData
+        ((channelCount == 1 ? m_context.channel : channel),
+         startFrame, size, buffer + offset);
+
+    while (got < size) {
+        buffer[offset + got] = 0.0;
+        ++got;
+    }
+
+    if (m_context.channel == -1 && channelCount == 1 &&
+        getInput()->getChannelCount() > 1) {
+        // use mean instead of sum, as plugin input
+        int cc = getInput()->getChannelCount();
+        for (long i = 0; i < size; ++i) {
+            buffer[i] /= cc;
+        }
+    }
+}
+
+void
+FeatureExtractionPluginTransform::addFeature(size_t blockFrame,
+					     const Vamp::Plugin::Feature &feature)
+{
+    size_t inputRate = m_input->getSampleRate();
+
+//    std::cerr << "FeatureExtractionPluginTransform::addFeature("
+//	      << blockFrame << ")" << std::endl;
+
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+    size_t frame = blockFrame;
+
+    if (m_descriptor->sampleType ==
+	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	if (!feature.hasTimestamp) {
+	    std::cerr
+		<< "WARNING: FeatureExtractionPluginTransform::addFeature: "
+		<< "Feature has variable sample rate but no timestamp!"
+		<< std::endl;
+	    return;
+	} else {
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
+	}
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
+
+	if (feature.hasTimestamp) {
+	    //!!! warning: sampleRate may be non-integral
+	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp,
+                                                   lrintf(m_descriptor->sampleRate));
+	} else {
+	    frame = m_output->getEndFrame();
+	}
+    }
+	
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
+	if (!model) return;
+	model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str()));
+	
+    } else if (binCount == 1) {
+
+	float value = 0.0;
+	if (feature.values.size() > 0) value = feature.values[0];
+
+	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
+	if (!model) return;
+	model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str()));
+//        std::cerr << "SparseTimeValueModel::addPoint(" << frame << ", " << value << "), " << feature.label.c_str() << std::endl;
+
+    } else if (m_descriptor->sampleType == 
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+        float pitch = 0.0;
+        if (feature.values.size() > 0) pitch = feature.values[0];
+
+        float duration = 1;
+        if (feature.values.size() > 1) duration = feature.values[1];
+        
+        float velocity = 100;
+        if (feature.values.size() > 2) velocity = feature.values[2];
+
+        NoteModel *model = getOutput<NoteModel>();
+        if (!model) return;
+
+        model->addPoint(NoteModel::Point(frame, pitch,
+                                         lrintf(duration),
+                                         feature.label.c_str()));
+	
+    } else {
+	
+	DenseThreeDimensionalModel::Column values = feature.values;
+	
+	EditableDenseThreeDimensionalModel *model =
+            getOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+
+	model->setColumn(frame / model->getResolution(), values);
+    }
+}
+
+void
+FeatureExtractionPluginTransform::setCompletion(int completion)
+{
+    int binCount = 1;
+    if (m_descriptor->hasFixedBinCount) {
+	binCount = m_descriptor->binCount;
+    }
+
+//    std::cerr << "FeatureExtractionPluginTransform::setCompletion("
+//              << completion << ")" << std::endl;
+
+    if (binCount == 0) {
+
+	SparseOneDimensionalModel *model = getOutput<SparseOneDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else if (binCount == 1) {
+
+	SparseTimeValueModel *model = getOutput<SparseTimeValueModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else if (m_descriptor->sampleType ==
+	       Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+
+	NoteModel *model = getOutput<NoteModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+
+    } else {
+
+	EditableDenseThreeDimensionalModel *model =
+            getOutput<EditableDenseThreeDimensionalModel>();
+	if (!model) return;
+	model->setCompletion(completion);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/FeatureExtractionPluginTransform.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,62 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _FEATURE_EXTRACTION_PLUGIN_TRANSFORM_H_
+#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORM_H_
+
+#include "PluginTransform.h"
+
+class DenseTimeValueModel;
+
+class FeatureExtractionPluginTransform : public PluginTransform
+{
+    Q_OBJECT
+
+public:
+    FeatureExtractionPluginTransform(Model *inputModel,
+				     QString plugin,
+                                     const ExecutionContext &context,
+                                     QString configurationXml = "",
+				     QString outputName = "");
+    virtual ~FeatureExtractionPluginTransform();
+
+protected:
+    virtual void run();
+
+    Vamp::Plugin *m_plugin;
+    Vamp::Plugin::OutputDescriptor *m_descriptor;
+    int m_outputFeatureNo;
+
+    void addFeature(size_t blockFrame,
+		    const Vamp::Plugin::Feature &feature);
+
+    void setCompletion(int);
+
+    void getFrames(int channel, int channelCount,
+                   long startFrame, long size, float *buffer);
+
+    // just casts
+    DenseTimeValueModel *getInput();
+    template <typename ModelClass> ModelClass *getOutput() {
+	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
+	if (!mc) {
+	    std::cerr << "FeatureExtractionPluginTransform::getOutput: Output model not conformable" << std::endl;
+	}
+	return mc;
+    }
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/PluginTransform.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,121 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "PluginTransform.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+#include "vamp-sdk/hostext/PluginWrapper.h"
+
+PluginTransform::PluginTransform(Model *inputModel,
+				 const ExecutionContext &context) :
+    Transform(inputModel),
+    m_context(context)
+{
+}
+
+PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _bs) :
+    channel(_c),
+    domain(Vamp::Plugin::TimeDomain),
+    stepSize(_bs ? _bs : 1024),
+    blockSize(_bs ? _bs : 1024),
+    windowType(HanningWindow),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+}
+
+PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _ss,
+                                                    size_t _bs, WindowType _wt) :
+    channel(_c),
+    domain(Vamp::Plugin::FrequencyDomain),
+    stepSize(_ss ? _ss : (_bs ? _bs / 2 : 512)),
+    blockSize(_bs ? _bs : 1024),
+    windowType(_wt),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+}
+
+PluginTransform::ExecutionContext::ExecutionContext(int _c,
+                                                    const Vamp::PluginBase *_plugin) :
+    channel(_c),
+    domain(Vamp::Plugin::TimeDomain),
+    stepSize(0),
+    blockSize(0),
+    windowType(HanningWindow),
+    startFrame(0),
+    duration(0),
+    sampleRate(0)
+{
+    makeConsistentWithPlugin(_plugin);
+}
+
+bool
+PluginTransform::ExecutionContext::operator==(const ExecutionContext &c)
+{
+    return (c.channel == channel &&
+            c.domain == domain &&
+            c.stepSize == stepSize &&
+            c.blockSize == blockSize &&
+            c.windowType == windowType &&
+            c.startFrame == startFrame &&
+            c.duration == duration &&
+            c.sampleRate == sampleRate);
+}
+
+void
+PluginTransform::ExecutionContext::makeConsistentWithPlugin(const Vamp::PluginBase *_plugin)
+{
+    const Vamp::Plugin *vp = dynamic_cast<const Vamp::Plugin *>(_plugin);
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl;
+        vp = dynamic_cast<const Vamp::PluginHostAdapter *>(_plugin); //!!! why?
+}
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl;
+        vp = dynamic_cast<const Vamp::HostExt::PluginWrapper *>(_plugin); //!!! no, I mean really why?
+    }
+    if (!vp) {
+//        std::cerr << "makeConsistentWithPlugin: not a Vamp::HostExt::PluginWrapper" << std::endl;
+    }
+
+    if (!vp) {
+        domain = Vamp::Plugin::TimeDomain;
+        if (!stepSize) {
+            if (!blockSize) blockSize = 1024;
+            stepSize = blockSize;
+        } else {
+            if (!blockSize) blockSize = stepSize;
+        }
+    } else {
+        domain = vp->getInputDomain();
+        if (!stepSize) stepSize = vp->getPreferredStepSize();
+        if (!blockSize) blockSize = vp->getPreferredBlockSize();
+        if (!blockSize) blockSize = 1024;
+        if (!stepSize) {
+            if (domain == Vamp::Plugin::FrequencyDomain) {
+//                std::cerr << "frequency domain, step = " << blockSize/2 << std::endl;
+                stepSize = blockSize/2;
+            } else {
+//                std::cerr << "time domain, step = " << blockSize/2 << std::endl;
+                stepSize = blockSize;
+            }
+        }
+    }
+}
+    
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/PluginTransform.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,63 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _PLUGIN_TRANSFORM_H_
+#define _PLUGIN_TRANSFORM_H_
+
+#include "Transform.h"
+
+#include "base/Window.h"
+
+#include "vamp-sdk/Plugin.h"
+
+//!!! should this just move back up to Transform? It is after all used
+//directly in all sorts of generic places, like Document
+
+class PluginTransform : public Transform
+{
+public:
+    class ExecutionContext {
+    public:
+        // Time domain:
+        ExecutionContext(int _c = -1, size_t _bs = 0);
+        
+        // Frequency domain:
+        ExecutionContext(int _c, size_t _ss, size_t _bs, WindowType _wt);
+
+        // From plugin defaults:
+        ExecutionContext(int _c, const Vamp::PluginBase *_plugin);
+
+        bool operator==(const ExecutionContext &);
+
+        void makeConsistentWithPlugin(const Vamp::PluginBase *_plugin);
+
+        int channel;
+        Vamp::Plugin::InputDomain domain;
+        size_t stepSize;
+        size_t blockSize;
+        WindowType windowType;
+        size_t startFrame;
+        size_t duration;    // 0 -> whole thing
+        float sampleRate;   // 0 -> model's rate
+    };
+
+protected:
+    PluginTransform(Model *inputModel,
+                    const ExecutionContext &context);
+
+    ExecutionContext m_context;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimePluginTransform.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,274 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "RealTimePluginTransform.h"
+
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/RealTimePluginInstance.h"
+#include "plugin/PluginXml.h"
+
+#include "data/model/Model.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/DenseTimeValueModel.h"
+#include "data/model/WritableWaveFileModel.h"
+#include "data/model/WaveFileModel.h"
+
+#include <iostream>
+
+RealTimePluginTransform::RealTimePluginTransform(Model *inputModel,
+                                                 QString pluginId,
+                                                 const ExecutionContext &context,
+                                                 QString configurationXml,
+                                                 QString units,
+                                                 int output) :
+    PluginTransform(inputModel, context),
+    m_pluginId(pluginId),
+    m_configurationXml(configurationXml),
+    m_units(units),
+    m_plugin(0),
+    m_outputNo(output)
+{
+    if (!m_context.blockSize) m_context.blockSize = 1024;
+
+//    std::cerr << "RealTimePluginTransform::RealTimePluginTransform: plugin " << pluginId.toStdString() << ", output " << output << std::endl;
+
+    RealTimePluginFactory *factory =
+	RealTimePluginFactory::instanceFor(pluginId);
+
+    if (!factory) {
+	std::cerr << "RealTimePluginTransform: No factory available for plugin id \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
+                                          m_input->getSampleRate(),
+                                          m_context.blockSize,
+                                          input->getChannelCount());
+
+    if (!m_plugin) {
+	std::cerr << "RealTimePluginTransform: Failed to instantiate plugin \""
+		  << pluginId.toStdString() << "\"" << std::endl;
+	return;
+    }
+
+    if (configurationXml != "") {
+        PluginXml(m_plugin).setParametersFromXml(configurationXml);
+    }
+
+    if (m_outputNo >= 0 &&
+        m_outputNo >= int(m_plugin->getControlOutputCount())) {
+        std::cerr << "RealTimePluginTransform: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
+        return;
+    }
+
+    if (m_outputNo == -1) {
+
+        size_t outputChannels = m_plugin->getAudioOutputCount();
+        if (outputChannels > input->getChannelCount()) {
+            outputChannels = input->getChannelCount();
+        }
+
+        WritableWaveFileModel *model = new WritableWaveFileModel
+            (input->getSampleRate(), outputChannels);
+
+        m_output = model;
+
+    } else {
+	
+        SparseTimeValueModel *model = new SparseTimeValueModel
+            (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false);
+
+        if (units != "") model->setScaleUnits(units);
+
+        m_output = model;
+    }
+}
+
+RealTimePluginTransform::~RealTimePluginTransform()
+{
+    delete m_plugin;
+}
+
+DenseTimeValueModel *
+RealTimePluginTransform::getInput()
+{
+    DenseTimeValueModel *dtvm =
+	dynamic_cast<DenseTimeValueModel *>(getInputModel());
+    if (!dtvm) {
+	std::cerr << "RealTimePluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl;
+    }
+    return dtvm;
+}
+
+void
+RealTimePluginTransform::run()
+{
+    DenseTimeValueModel *input = getInput();
+    if (!input) return;
+
+    while (!input->isReady()) {
+        if (dynamic_cast<WaveFileModel *>(input)) break; // no need to wait
+        std::cerr << "RealTimePluginTransform::run: Waiting for input model to be ready..." << std::endl;
+        sleep(1);
+    }
+
+    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
+    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
+    if (!stvm && !wwfm) return;
+
+    if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
+
+    size_t sampleRate = input->getSampleRate();
+    size_t channelCount = input->getChannelCount();
+    if (!wwfm && m_context.channel != -1) channelCount = 1;
+
+    long blockSize = m_plugin->getBufferSize();
+
+    float **inbufs = m_plugin->getAudioInputBuffers();
+
+    long startFrame = m_input->getStartFrame();
+    long   endFrame = m_input->getEndFrame();
+    
+    long contextStart = m_context.startFrame;
+    long contextDuration = m_context.duration;
+
+    if (contextStart == 0 || contextStart < startFrame) {
+        contextStart = startFrame;
+    }
+
+    if (contextDuration == 0) {
+        contextDuration = endFrame - contextStart;
+    }
+    if (contextStart + contextDuration > endFrame) {
+        contextDuration = endFrame - contextStart;
+    }
+
+    wwfm->setStartFrame(contextStart);
+
+    long blockFrame = contextStart;
+
+    long prevCompletion = 0;
+
+    long latency = m_plugin->getLatency();
+
+    while (blockFrame < contextStart + contextDuration + latency &&
+           !m_abandoned) {
+
+	long completion =
+	    (((blockFrame - contextStart) / blockSize) * 99) /
+	    ((contextDuration) / blockSize);
+
+	long got = 0;
+
+	if (channelCount == 1) {
+            if (inbufs && inbufs[0]) {
+                got = input->getData
+                    (m_context.channel, blockFrame, blockSize, inbufs[0]);
+                while (got < blockSize) {
+                    inbufs[0][got++] = 0.0;
+                }          
+            }
+            for (size_t ch = 1; ch < m_plugin->getAudioInputCount(); ++ch) {
+                for (long i = 0; i < blockSize; ++i) {
+                    inbufs[ch][i] = inbufs[0][i];
+                }
+            }
+	} else {
+	    for (size_t ch = 0; ch < channelCount; ++ch) {
+                if (inbufs && inbufs[ch]) {
+                    got = input->getData
+                        (ch, blockFrame, blockSize, inbufs[ch]);
+                    while (got < blockSize) {
+                        inbufs[ch][got++] = 0.0;
+                    }
+                }
+	    }
+            for (size_t ch = channelCount; ch < m_plugin->getAudioInputCount(); ++ch) {
+                for (long i = 0; i < blockSize; ++i) {
+                    inbufs[ch][i] = inbufs[ch % channelCount][i];
+                }
+            }
+	}
+
+/*
+        std::cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< std::endl;
+
+        for (size_t ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) {
+            std::cerr << "Input channel " << ch << std::endl;
+            for (size_t i = 0; i < 100; ++i) {
+                std::cerr << inbufs[ch][i] << " ";
+                if (isnan(inbufs[ch][i])) {
+                    std::cerr << "\n\nWARNING: NaN in audio input" << std::endl;
+                }
+            }
+        }
+*/
+
+        m_plugin->run(Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+
+        if (stvm) {
+
+            float value = m_plugin->getControlOutputValue(m_outputNo);
+
+            long pointFrame = blockFrame;
+            if (pointFrame > latency) pointFrame -= latency;
+            else pointFrame = 0;
+
+            stvm->addPoint(SparseTimeValueModel::Point
+                           (pointFrame, value, ""));
+
+        } else if (wwfm) {
+
+            float **outbufs = m_plugin->getAudioOutputBuffers();
+
+            if (outbufs) {
+
+                if (blockFrame >= latency) {
+                    long writeSize = std::min
+                        (blockSize,
+                         contextStart + contextDuration + latency - blockFrame);
+                    wwfm->addSamples(outbufs, writeSize);
+                } else if (blockFrame + blockSize >= latency) {
+                    long offset = latency - blockFrame;
+                    long count = blockSize - offset;
+                    float **tmp = new float *[channelCount];
+                    for (size_t c = 0; c < channelCount; ++c) {
+                        tmp[c] = outbufs[c] + offset;
+                    }
+                    wwfm->addSamples(tmp, count);
+                    delete[] tmp;
+                }
+            }
+        }
+
+	if (blockFrame == contextStart || completion > prevCompletion) {
+	    if (stvm) stvm->setCompletion(completion);
+	    if (wwfm) wwfm->setCompletion(completion);
+	    prevCompletion = completion;
+	}
+        
+	blockFrame += blockSize;
+    }
+
+    if (m_abandoned) return;
+    
+    if (stvm) stvm->setCompletion(100);
+    if (wwfm) wwfm->setCompletion(100);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/RealTimePluginTransform.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _REAL_TIME_PLUGIN_TRANSFORM_H_
+#define _REAL_TIME_PLUGIN_TRANSFORM_H_
+
+#include "PluginTransform.h"
+#include "plugin/RealTimePluginInstance.h"
+
+class DenseTimeValueModel;
+
+class RealTimePluginTransform : public PluginTransform
+{
+public:
+    RealTimePluginTransform(Model *inputModel,
+			    QString plugin,
+                            const ExecutionContext &context,
+			    QString configurationXml = "",
+                            QString units = "",
+			    int output = -1); // -1 -> audio, 0+ -> data
+    virtual ~RealTimePluginTransform();
+
+protected:
+    virtual void run();
+
+    QString m_pluginId;
+    QString m_configurationXml;
+    QString m_units;
+
+    RealTimePluginInstance *m_plugin;
+    int m_outputNo;
+
+    // just casts
+    DenseTimeValueModel *getInput();
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/Transform.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,32 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "Transform.h"
+
+Transform::Transform(Model *m) :
+    m_input(m),
+    m_output(0),
+    m_detached(false),
+    m_abandoned(false)
+{
+}
+
+Transform::~Transform()
+{
+    m_abandoned = true;
+    wait();
+    if (!m_detached) delete m_output;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/Transform.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,61 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _TRANSFORM_H_
+#define _TRANSFORM_H_
+
+#include "base/Thread.h"
+
+#include "data/model/Model.h"
+
+typedef QString TransformId;
+
+/**
+ * A Transform turns one data model into another.
+ *
+ * Typically in this application, a Transform might have a
+ * DenseTimeValueModel as its input (e.g. an audio waveform) and a
+ * SparseOneDimensionalModel (e.g. detected beats) as its output.
+ *
+ * The Transform typically runs in the background, as a separate
+ * thread populating the output model.  The model is available to the
+ * user of the Transform immediately, but may be initially empty until
+ * the background thread has populated it.
+ */
+
+class Transform : public Thread
+{
+public:
+    virtual ~Transform();
+
+    // Just a hint to the processing thread that it should give up.
+    // Caller should still wait() and/or delete the transform before
+    // assuming its input and output models are no longer required.
+    void abandon() { m_abandoned = true; }
+
+    Model *getInputModel()  { return m_input; }
+    Model *getOutputModel() { return m_output; }
+    Model *detachOutputModel() { m_detached = true; return m_output; }
+
+protected:
+    Transform(Model *m);
+
+    Model *m_input; // I don't own this
+    Model *m_output; // I own this, unless...
+    bool m_detached; // ... this is true.
+    bool m_abandoned;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/TransformFactory.cpp	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,861 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "TransformFactory.h"
+
+#include "FeatureExtractionPluginTransform.h"
+#include "RealTimePluginTransform.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/PluginXml.h"
+
+#include "widgets/PluginParameterDialog.h"
+
+#include "data/model/DenseTimeValueModel.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+
+#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
+
+#include <iostream>
+#include <set>
+
+#include <QRegExp>
+
+TransformFactory *
+TransformFactory::m_instance = new TransformFactory;
+
+TransformFactory *
+TransformFactory::getInstance()
+{
+    return m_instance;
+}
+
+TransformFactory::~TransformFactory()
+{
+}
+
+TransformFactory::TransformList
+TransformFactory::getAllTransforms()
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<TransformDesc> dset;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+	dset.insert(i->second);
+    }
+
+    TransformList list;
+    for (std::set<TransformDesc>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+	list.push_back(*i);
+    }
+
+    return list;
+}
+
+std::vector<QString>
+TransformFactory::getAllTransformTypes()
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> types;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+        types.insert(i->second.type);
+    }
+
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = types.begin(); i != types.end(); ++i) {
+        rv.push_back(*i);
+    }
+
+    return rv;
+}
+
+std::vector<QString>
+TransformFactory::getTransformCategories(QString transformType)
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> categories;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
+        if (i->second.type == transformType) {
+            categories.insert(i->second.category);
+        }
+    }
+
+    bool haveEmpty = false;
+    
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = categories.begin(); 
+         i != categories.end(); ++i) {
+        if (*i != "") rv.push_back(*i);
+        else haveEmpty = true;
+    }
+
+    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
+
+    return rv;
+}
+
+std::vector<QString>
+TransformFactory::getTransformMakers(QString transformType)
+{
+    if (m_transforms.empty()) populateTransforms();
+
+    std::set<QString> makers;
+    for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
+         i != m_transforms.end(); ++i) {
+        if (i->second.type == transformType) {
+            makers.insert(i->second.maker);
+        }
+    }
+
+    bool haveEmpty = false;
+    
+    std::vector<QString> rv;
+    for (std::set<QString>::iterator i = makers.begin(); 
+         i != makers.end(); ++i) {
+        if (*i != "") rv.push_back(*i);
+        else haveEmpty = true;
+    }
+
+    if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
+
+    return rv;
+}
+
+void
+TransformFactory::populateTransforms()
+{
+    TransformDescriptionMap transforms;
+
+    populateFeatureExtractionPlugins(transforms);
+    populateRealTimePlugins(transforms);
+
+    // disambiguate plugins with similar names
+
+    std::map<QString, int> names;
+    std::map<QString, QString> pluginSources;
+    std::map<QString, QString> pluginMakers;
+
+    for (TransformDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformDesc desc = i->second;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString pn = desc.identifier.section(":", 1, 1);
+
+        if (pluginSources.find(tn) != pluginSources.end()) {
+            if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) {
+                ++names[tn];
+            }
+        } else {
+            ++names[tn];
+            pluginSources[tn] = pn;
+            pluginMakers[tn] = desc.maker;
+        }
+    }
+
+    std::map<QString, int> counts;
+    m_transforms.clear();
+
+    for (TransformDescriptionMap::iterator i = transforms.begin();
+         i != transforms.end(); ++i) {
+
+        TransformDesc desc = i->second;
+	QString identifier = desc.identifier;
+        QString maker = desc.maker;
+
+        QString td = desc.name;
+        QString tn = td.section(": ", 0, 0);
+        QString to = td.section(": ", 1);
+
+	if (names[tn] > 1) {
+            maker.replace(QRegExp(tr(" [\\(<].*$")), "");
+	    tn = QString("%1 [%2]").arg(tn).arg(maker);
+	}
+
+        if (to != "") {
+            desc.name = QString("%1: %2").arg(tn).arg(to);
+        } else {
+            desc.name = tn;
+        }
+
+	m_transforms[identifier] = desc;
+    }	    
+}
+
+void
+TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
+{
+    std::vector<QString> plugs =
+	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
+
+    for (size_t i = 0; i < plugs.size(); ++i) {
+
+	QString pluginId = plugs[i];
+
+	FeatureExtractionPluginFactory *factory =
+	    FeatureExtractionPluginFactory::instanceFor(pluginId);
+
+	if (!factory) {
+	    std::cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+
+	Vamp::Plugin *plugin = 
+	    factory->instantiatePlugin(pluginId, 48000);
+
+	if (!plugin) {
+	    std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+		
+	QString pluginName = plugin->getName().c_str();
+        QString category = factory->getPluginCategory(pluginId);
+
+	Vamp::Plugin::OutputList outputs =
+	    plugin->getOutputDescriptors();
+
+	for (size_t j = 0; j < outputs.size(); ++j) {
+
+	    QString transformId = QString("%1:%2")
+		    .arg(pluginId).arg(outputs[j].identifier.c_str());
+
+	    QString userName;
+            QString friendlyName;
+            QString units = outputs[j].unit.c_str();
+            QString description = plugin->getDescription().c_str();
+            QString maker = plugin->getMaker().c_str();
+            if (maker == "") maker = tr("<unknown maker>");
+
+            if (description == "") {
+                if (outputs.size() == 1) {
+                    description = tr("Extract features using \"%1\" plugin (from %2)")
+                        .arg(pluginName).arg(maker);
+                } else {
+                    description = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)")
+                        .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                }
+            } else {
+                if (outputs.size() == 1) {
+                    description = tr("%1 using \"%2\" plugin (from %3)")
+                        .arg(description).arg(pluginName).arg(maker);
+                } else {
+                    description = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)")
+                        .arg(description).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                }
+            }                    
+
+	    if (outputs.size() == 1) {
+		userName = pluginName;
+                friendlyName = pluginName;
+	    } else {
+		userName = QString("%1: %2")
+		    .arg(pluginName)
+		    .arg(outputs[j].name.c_str());
+                friendlyName = outputs[j].name.c_str();
+	    }
+
+            bool configurable = (!plugin->getPrograms().empty() ||
+                                 !plugin->getParameterDescriptors().empty());
+
+//            std::cerr << "Feature extraction plugin transform: " << transformId.toStdString() << std::endl;
+
+	    transforms[transformId] = 
+                TransformDesc(tr("Analysis"),
+                              category,
+                              transformId,
+                              userName,
+                              friendlyName,
+                              description,
+                              maker,
+                              units,
+                              configurable);
+	}
+
+        delete plugin;
+    }
+}
+
+void
+TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms)
+{
+    std::vector<QString> plugs =
+	RealTimePluginFactory::getAllPluginIdentifiers();
+
+    static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
+
+    for (size_t i = 0; i < plugs.size(); ++i) {
+        
+	QString pluginId = plugs[i];
+
+        RealTimePluginFactory *factory =
+            RealTimePluginFactory::instanceFor(pluginId);
+
+	if (!factory) {
+	    std::cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+
+        const RealTimePluginDescriptor *descriptor =
+            factory->getPluginDescriptor(pluginId);
+
+        if (!descriptor) {
+	    std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl;
+	    continue;
+	}
+	
+//!!!        if (descriptor->controlOutputPortCount == 0 ||
+//            descriptor->audioInputPortCount == 0) continue;
+
+//        std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl;
+	
+	QString pluginName = descriptor->name.c_str();
+        QString category = factory->getPluginCategory(pluginId);
+        bool configurable = (descriptor->parameterCount > 0);
+        QString maker = descriptor->maker.c_str();
+        if (maker == "") maker = tr("<unknown maker>");
+
+        if (descriptor->audioInputPortCount > 0) {
+
+            for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
+
+                QString transformId = QString("%1:%2").arg(pluginId).arg(j);
+                QString userName;
+                QString units;
+                QString portName;
+
+                if (j < descriptor->controlOutputPortNames.size() &&
+                    descriptor->controlOutputPortNames[j] != "") {
+
+                    portName = descriptor->controlOutputPortNames[j].c_str();
+
+                    userName = tr("%1: %2")
+                        .arg(pluginName)
+                        .arg(portName);
+
+                    if (unitRE.indexIn(portName) >= 0) {
+                        units = unitRE.cap(1);
+                    }
+
+                } else if (descriptor->controlOutputPortCount > 1) {
+
+                    userName = tr("%1: Output %2")
+                        .arg(pluginName)
+                        .arg(j + 1);
+
+                } else {
+
+                    userName = pluginName;
+                }
+
+                QString description;
+
+                if (portName != "") {
+                    description = tr("Extract \"%1\" data output from \"%2\" effect plugin (from %3)")
+                        .arg(portName)
+                        .arg(pluginName)
+                        .arg(maker);
+                } else {
+                    description = tr("Extract data output %1 from \"%2\" effect plugin (from %3)")
+                        .arg(j + 1)
+                        .arg(pluginName)
+                        .arg(maker);
+                }
+
+                transforms[transformId] = 
+                    TransformDesc(tr("Effects Data"),
+                                  category,
+                                  transformId,
+                                  userName,
+                                  userName,
+                                  description,
+                                  maker,
+                                  units,
+                                  configurable);
+            }
+        }
+
+        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
+
+            if (descriptor->audioOutputPortCount > 0) {
+
+                QString transformId = QString("%1:A").arg(pluginId);
+                QString type = tr("Effects");
+
+                QString description = tr("Transform audio signal with \"%1\" effect plugin (from %2)")
+                    .arg(pluginName)
+                    .arg(maker);
+
+                if (descriptor->audioInputPortCount == 0) {
+                    type = tr("Generators");
+                    QString description = tr("Generate audio signal using \"%1\" plugin (from %2)")
+                        .arg(pluginName)
+                        .arg(maker);
+                }
+
+                transforms[transformId] =
+                    TransformDesc(type,
+                                  category,
+                                  transformId,
+                                  pluginName,
+                                  pluginName,
+                                  description,
+                                  maker,
+                                  "",
+                                  configurable);
+            }
+        }
+    }
+}
+
+QString
+TransformFactory::getTransformName(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].name;
+    } else return "";
+}
+
+QString
+TransformFactory::getTransformFriendlyName(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].friendlyName;
+    } else return "";
+}
+
+QString
+TransformFactory::getTransformUnits(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].units;
+    } else return "";
+}
+
+bool
+TransformFactory::isTransformConfigurable(TransformId identifier)
+{
+    if (m_transforms.find(identifier) != m_transforms.end()) {
+	return m_transforms[identifier].configurable;
+    } else return false;
+}
+
+bool
+TransformFactory::getTransformChannelRange(TransformId identifier,
+                                           int &min, int &max)
+{
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *plugin = 
+            FeatureExtractionPluginFactory::instanceFor(id)->
+            instantiatePlugin(id, 48000);
+        if (!plugin) return false;
+
+        min = plugin->getMinChannelCount();
+        max = plugin->getMaxChannelCount();
+        delete plugin;
+
+        return true;
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        const RealTimePluginDescriptor *descriptor = 
+            RealTimePluginFactory::instanceFor(id)->
+            getPluginDescriptor(id);
+        if (!descriptor) return false;
+
+        min = descriptor->audioInputPortCount;
+        max = descriptor->audioInputPortCount;
+
+        return true;
+    }
+
+    return false;
+}
+
+bool
+TransformFactory::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin,
+                                  int &minChannels, int &maxChannels)
+{
+    Vamp::Plugin *vp = 0;
+    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
+        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
+        minChannels = vp->getMinChannelCount();
+        maxChannels = vp->getMaxChannelCount();
+        return true;
+    } else {
+        return getTransformChannelRange(identifier, minChannels, maxChannels);
+    }
+}
+
+Model *
+TransformFactory::getConfigurationForTransform(TransformId identifier,
+                                               const std::vector<Model *> &candidateInputModels,
+                                               PluginTransform::ExecutionContext &context,
+                                               QString &configurationXml,
+                                               AudioCallbackPlaySource *source,
+                                               size_t startFrame,
+                                               size_t duration)
+{
+    if (candidateInputModels.empty()) return 0;
+
+    //!!! This will need revision -- we'll have to have a callback
+    //from the dialog for when the candidate input model is changed,
+    //as we'll need to reinitialise the channel settings in the dialog
+    Model *inputModel = candidateInputModels[0]; //!!! for now
+    QStringList candidateModelNames;
+    std::map<QString, Model *> modelMap;
+    for (size_t i = 0; i < candidateInputModels.size(); ++i) {
+        QString modelName = candidateInputModels[i]->objectName();
+        QString origModelName = modelName;
+        int dupcount = 1;
+        while (modelMap.find(modelName) != modelMap.end()) {
+            modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
+        }
+        modelMap[modelName] = candidateInputModels[i];
+        candidateModelNames.push_back(modelName);
+    }
+
+    QString id = identifier.section(':', 0, 2);
+    QString output = identifier.section(':', 3);
+    QString outputLabel = "";
+    QString outputDescription = "";
+    
+    bool ok = false;
+    configurationXml = m_lastConfigurations[identifier];
+
+//    std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
+
+    Vamp::PluginBase *plugin = 0;
+
+    bool frequency = false;
+    bool effect = false;
+    bool generator = false;
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        std::cerr << "getConfigurationForTransform: instantiating Vamp plugin" << std::endl;
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel->getSampleRate());
+
+        if (vp) {
+
+            plugin = vp;
+            frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
+
+            std::vector<Vamp::Plugin::OutputDescriptor> od =
+                vp->getOutputDescriptors();
+            if (od.size() > 1) {
+                for (size_t i = 0; i < od.size(); ++i) {
+                    if (od[i].identifier == output.toStdString()) {
+                        outputLabel = od[i].name.c_str();
+                        outputDescription = od[i].description.c_str();
+                        break;
+                    }
+                }
+            }
+        }
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
+        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
+
+        if (desc->audioInputPortCount > 0 && 
+            desc->audioOutputPortCount > 0 &&
+            !desc->isSynth) {
+            effect = true;
+        }
+
+        if (desc->audioInputPortCount == 0) {
+            generator = true;
+        }
+
+        if (output != "A") {
+            int outputNo = output.toInt();
+            if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
+                outputLabel = desc->controlOutputPortNames[outputNo].c_str();
+            }
+        }
+
+        size_t sampleRate = inputModel->getSampleRate();
+        size_t blockSize = 1024;
+        size_t channels = 1;
+        if (effect && source) {
+            sampleRate = source->getTargetSampleRate();
+            blockSize = source->getTargetBlockSize();
+            channels = source->getTargetChannelCount();
+        }
+
+        RealTimePluginInstance *rtp = factory->instantiatePlugin
+            (id, 0, 0, sampleRate, blockSize, channels);
+
+        plugin = rtp;
+
+        if (effect && source && rtp) {
+            source->setAuditioningPlugin(rtp);
+        }
+    }
+
+    if (plugin) {
+
+        context = PluginTransform::ExecutionContext(context.channel, plugin);
+
+        if (configurationXml != "") {
+            PluginXml(plugin).setParametersFromXml(configurationXml);
+        }
+
+        int sourceChannels = 1;
+        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
+            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
+                ->getChannelCount();
+        }
+
+        int minChannels = 1, maxChannels = sourceChannels;
+        getChannelRange(identifier, plugin, minChannels, maxChannels);
+
+        int targetChannels = sourceChannels;
+        if (!effect) {
+            if (sourceChannels < minChannels) targetChannels = minChannels;
+            if (sourceChannels > maxChannels) targetChannels = maxChannels;
+        }
+
+        int defaultChannel = context.channel;
+
+        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
+
+        if (candidateModelNames.size() > 1 && !generator) {
+            dialog->setCandidateInputModels(candidateModelNames);
+        }
+
+        if (startFrame != 0 || duration != 0) {
+            dialog->setShowSelectionOnlyOption(true);
+        }
+
+        if (targetChannels > 0) {
+            dialog->setChannelArrangement(sourceChannels, targetChannels,
+                                          defaultChannel);
+        }
+        
+        dialog->setOutputLabel(outputLabel, outputDescription);
+        
+        dialog->setShowProcessingOptions(true, frequency);
+
+        if (dialog->exec() == QDialog::Accepted) {
+            ok = true;
+        }
+
+        QString selectedInput = dialog->getInputModel();
+        if (selectedInput != "") {
+            if (modelMap.find(selectedInput) != modelMap.end()) {
+                inputModel = modelMap[selectedInput];
+                std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
+            } else {
+                std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
+            }
+        } else {
+            std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
+        }
+
+        configurationXml = PluginXml(plugin).toXmlString();
+        context.channel = dialog->getChannel();
+        
+        if (startFrame != 0 || duration != 0) {
+            if (dialog->getSelectionOnly()) {
+                context.startFrame = startFrame;
+                context.duration = duration;
+            }
+        }
+
+        dialog->getProcessingParameters(context.stepSize,
+                                        context.blockSize,
+                                        context.windowType);
+
+        context.makeConsistentWithPlugin(plugin);
+
+        delete dialog;
+
+        if (effect && source) {
+            source->setAuditioningPlugin(0); // will delete our plugin
+        } else {
+            delete plugin;
+        }
+    }
+
+    if (ok) m_lastConfigurations[identifier] = configurationXml;
+
+    return ok ? inputModel : 0;
+}
+
+PluginTransform::ExecutionContext
+TransformFactory::getDefaultContextForTransform(TransformId identifier,
+                                                Model *inputModel)
+{
+    PluginTransform::ExecutionContext context(-1);
+
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel ? inputModel->getSampleRate() : 48000);
+
+        if (vp) {
+            context = PluginTransform::ExecutionContext(-1, vp);
+            delete vp;
+        }
+    }
+
+    return context;
+}
+
+Transform *
+TransformFactory::createTransform(TransformId identifier, Model *inputModel,
+                                  const PluginTransform::ExecutionContext &context,
+                                  QString configurationXml)
+{
+    Transform *transform = 0;
+
+    QString id = identifier.section(':', 0, 2);
+    QString output = identifier.section(':', 3);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+        transform = new FeatureExtractionPluginTransform(inputModel,
+                                                         id,
+                                                         context,
+                                                         configurationXml,
+                                                         output);
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+        transform = new RealTimePluginTransform(inputModel,
+                                                id,
+                                                context,
+                                                configurationXml,
+                                                getTransformUnits(identifier),
+                                                output == "A" ? -1 :
+                                                output.toInt());
+    } else {
+        std::cerr << "TransformFactory::createTransform: Unknown transform \""
+                  << identifier.toStdString() << "\"" << std::endl;
+        return transform;
+    }
+
+    if (transform) transform->setObjectName(identifier);
+    return transform;
+}
+
+Model *
+TransformFactory::transform(TransformId identifier, Model *inputModel,
+                            const PluginTransform::ExecutionContext &context,
+                            QString configurationXml)
+{
+    Transform *t = createTransform(identifier, inputModel, context,
+                                   configurationXml);
+
+    if (!t) return 0;
+
+    connect(t, SIGNAL(finished()), this, SLOT(transformFinished()));
+
+    m_runningTransforms.insert(t);
+
+    t->start();
+    Model *model = t->detachOutputModel();
+
+    if (model) {
+        QString imn = inputModel->objectName();
+        QString trn = getTransformFriendlyName(identifier);
+        if (imn != "") {
+            if (trn != "") {
+                model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
+            } else {
+                model->setObjectName(imn);
+            }
+        } else if (trn != "") {
+            model->setObjectName(trn);
+        }
+    } else {
+        t->wait();
+    }
+
+    return model;
+}
+
+void
+TransformFactory::transformFinished()
+{
+    QObject *s = sender();
+    Transform *transform = dynamic_cast<Transform *>(s);
+    
+    std::cerr << "TransformFactory::transformFinished(" << transform << ")" << std::endl;
+
+    if (!transform) {
+	std::cerr << "WARNING: TransformFactory::transformFinished: sender is not a transform" << std::endl;
+	return;
+    }
+
+    if (m_runningTransforms.find(transform) == m_runningTransforms.end()) {
+        std::cerr << "WARNING: TransformFactory::transformFinished(" 
+                  << transform
+                  << "): I have no record of this transform running!"
+                  << std::endl;
+    }
+
+    m_runningTransforms.erase(transform);
+
+    transform->wait(); // unnecessary but reassuring
+    delete transform;
+}
+
+void
+TransformFactory::modelAboutToBeDeleted(Model *m)
+{
+    TransformSet affected;
+
+    for (TransformSet::iterator i = m_runningTransforms.begin();
+         i != m_runningTransforms.end(); ++i) {
+
+        Transform *t = *i;
+
+        if (t->getInputModel() == m || t->getOutputModel() == m) {
+            affected.insert(t);
+        }
+    }
+
+    for (TransformSet::iterator i = affected.begin();
+         i != affected.end(); ++i) {
+
+        Transform *t = *i;
+
+        t->abandon();
+
+        t->wait(); // this should eventually call back on
+                   // transformFinished, which will remove from
+                   // m_runningTransforms and delete.
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/transform/TransformFactory.h	Wed Oct 24 16:34:31 2007 +0000
@@ -0,0 +1,185 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _TRANSFORM_FACTORY_H_
+#define _TRANSFORM_FACTORY_H_
+
+#include "Transform.h"
+#include "PluginTransform.h"
+
+#include <map>
+#include <set>
+
+namespace Vamp { class PluginBase; }
+
+class AudioCallbackPlaySource;
+
+class TransformFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~TransformFactory();
+
+    static TransformFactory *getInstance();
+
+    // The identifier is intended to be computer-referenceable, and
+    // unique within the application.  The name is intended to be
+    // human readable.  In principle it doesn't have to be unique, but
+    // the factory will add suffixes to ensure that it is, all the
+    // same (just to avoid user confusion).  The friendly name is a
+    // shorter version of the name.  The type is also intended to be
+    // user-readable, for use in menus.
+
+    struct TransformDesc {
+
+        TransformDesc() { }
+	TransformDesc(QString _type, QString _category,
+                      TransformId _identifier, QString _name,
+                      QString _friendlyName, QString _description,
+                      QString _maker, QString _units, bool _configurable) :
+	    type(_type), category(_category),
+            identifier(_identifier), name(_name),
+            friendlyName(_friendlyName), description(_description),
+            maker(_maker), units(_units), configurable(_configurable) { }
+
+        QString type; // e.g. feature extraction plugin
+        QString category; // e.g. time > onsets
+	TransformId identifier; // e.g. vamp:vamp-aubio:aubioonset
+	QString name; // plugin's name if 1 output, else "name: output"
+        QString friendlyName; // short text for layer name
+        QString description; // sentence describing transform
+        QString maker;
+        QString units;
+        bool configurable;
+
+        bool operator<(const TransformDesc &od) const {
+            return (name < od.name);
+        };
+    };
+    typedef std::vector<TransformDesc> TransformList;
+
+    TransformList getAllTransforms();
+
+    std::vector<QString> getAllTransformTypes();
+
+    std::vector<QString> getTransformCategories(QString transformType);
+    std::vector<QString> getTransformMakers(QString transformType);
+
+    /**
+     * Get a configuration XML string for the given transform (by
+     * asking the user, most likely).  Returns the selected input
+     * model if the transform is acceptable, 0 if the operation should
+     * be cancelled.  Audio callback play source may be used to
+     * audition effects plugins, if provided.
+     */
+    Model *getConfigurationForTransform(TransformId identifier,
+                                        const std::vector<Model *> &candidateInputModels,
+                                        PluginTransform::ExecutionContext &context,
+                                        QString &configurationXml,
+                                        AudioCallbackPlaySource *source = 0,
+                                        size_t startFrame = 0,
+                                        size_t duration = 0);
+
+    /**
+     * Get the default execution context for the given transform
+     * and input model (if known).
+     */
+    PluginTransform::ExecutionContext getDefaultContextForTransform(TransformId identifier,
+                                                                    Model *inputModel = 0);
+
+    /**
+     * Return the output model resulting from applying the named
+     * transform to the given input model.  The transform may still be
+     * working in the background when the model is returned; check the
+     * output model's isReady completion status for more details.
+     *
+     * If the transform is unknown or the input model is not an
+     * appropriate type for the given transform, or if some other
+     * problem occurs, return 0.
+     * 
+     * The returned model is owned by the caller and must be deleted
+     * when no longer needed.
+     */
+    Model *transform(TransformId identifier, Model *inputModel,
+                     const PluginTransform::ExecutionContext &context,
+                     QString configurationXml = "");
+
+    /**
+     * Full name of a transform, suitable for putting on a menu.
+     */
+    QString getTransformName(TransformId identifier);
+
+    /**
+     * Brief but friendly name of a transform, suitable for use
+     * as the name of the output layer.
+     */
+    QString getTransformFriendlyName(TransformId identifier);
+
+    QString getTransformUnits(TransformId identifier);
+
+    /**
+     * Return true if the transform has any configurable parameters,
+     * i.e. if getConfigurationForTransform can ever return a non-trivial
+     * (not equivalent to empty) configuration string.
+     */
+    bool isTransformConfigurable(TransformId identifier);
+
+    /**
+     * If the transform has a prescribed number or range of channel
+     * inputs, return true and set minChannels and maxChannels to the
+     * minimum and maximum number of channel inputs the transform can
+     * accept.  Return false if it doesn't care.
+     */
+    bool getTransformChannelRange(TransformId identifier,
+                                  int &minChannels, int &maxChannels);
+	
+protected slots:
+    void transformFinished();
+
+    void modelAboutToBeDeleted(Model *);
+
+protected:
+    Transform *createTransform(TransformId identifier, Model *inputModel,
+                               const PluginTransform::ExecutionContext &context,
+                               QString configurationXml);
+
+    struct TransformIdent
+    {
+        TransformId identifier;
+        QString configurationXml;
+    };
+
+    typedef std::map<TransformId, QString> TransformConfigurationMap;
+    TransformConfigurationMap m_lastConfigurations;
+
+    typedef std::map<TransformId, TransformDesc> TransformDescriptionMap;
+    TransformDescriptionMap m_transforms;
+
+    typedef std::set<Transform *> TransformSet;
+    TransformSet m_runningTransforms;
+
+    void populateTransforms();
+    void populateFeatureExtractionPlugins(TransformDescriptionMap &);
+    void populateRealTimePlugins(TransformDescriptionMap &);
+
+    bool getChannelRange(TransformId identifier,
+                         Vamp::PluginBase *plugin, int &min, int &max);
+
+    static TransformFactory *m_instance;
+};
+
+
+#endif