# HG changeset patch # User Chris Cannam # Date 1193243671 0 # Node ID 32e50b620a6cd48bffe94e9e176a47b18e1c12d9 # Parent 3ff8f571da090e3ad07a560ce56bf0c947848861 * 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) diff -r 3ff8f571da09 -r 32e50b620a6c data/data.pro --- 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 diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/OSCMessage.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]; +} + diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/OSCMessage.h --- /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 +#include + +#include +#include + +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 m_args; +}; + +#endif diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/OSCQueue.cpp --- /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 + +#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(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; +} + diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/OSCQueue.h --- /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 + +#ifdef HAVE_LIBLO +#include +#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 m_buffer; +}; + +#endif + diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/demoscript.sh --- /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 diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/sv-command --- /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 diff -r 3ff8f571da09 -r 32e50b620a6c data/osc/sv-osc-send.c --- /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 +#include +#include +#include +#include + +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 []\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; +} + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/plugin.pro --- 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 diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/FeatureExtractionPluginTransform.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 + +#include + +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 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(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(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 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(); + 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(); + 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(); + if (!model) return; + + model->addPoint(NoteModel::Point(frame, pitch, + lrintf(duration), + feature.label.c_str())); + + } else { + + DenseThreeDimensionalModel::Column values = feature.values; + + EditableDenseThreeDimensionalModel *model = + getOutput(); + 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(); + if (!model) return; + model->setCompletion(completion); + + } else if (binCount == 1) { + + SparseTimeValueModel *model = getOutput(); + if (!model) return; + model->setCompletion(completion); + + } else if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::VariableSampleRate) { + + NoteModel *model = getOutput(); + if (!model) return; + model->setCompletion(completion); + + } else { + + EditableDenseThreeDimensionalModel *model = + getOutput(); + if (!model) return; + model->setCompletion(completion); + } +} + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/FeatureExtractionPluginTransform.h --- /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 ModelClass *getOutput() { + ModelClass *mc = dynamic_cast(m_output); + if (!mc) { + std::cerr << "FeatureExtractionPluginTransform::getOutput: Output model not conformable" << std::endl; + } + return mc; + } +}; + +#endif + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/PluginTransform.cpp --- /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(_plugin); + if (!vp) { +// std::cerr << "makeConsistentWithPlugin: not a Vamp::Plugin" << std::endl; + vp = dynamic_cast(_plugin); //!!! why? +} + if (!vp) { +// std::cerr << "makeConsistentWithPlugin: not a Vamp::PluginHostAdapter" << std::endl; + vp = dynamic_cast(_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; + } + } + } +} + + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/PluginTransform.h --- /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 diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/RealTimePluginTransform.cpp --- /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 + +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(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(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(m_output); + WritableWaveFileModel *wwfm = dynamic_cast(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); +} + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/RealTimePluginTransform.h --- /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 + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/Transform.cpp --- /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; +} + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/Transform.h --- /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 diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/TransformFactory.cpp --- /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 +#include + +#include + +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 dset; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + dset.insert(i->second); + } + + TransformList list; + for (std::set::const_iterator i = dset.begin(); + i != dset.end(); ++i) { + list.push_back(*i); + } + + return list; +} + +std::vector +TransformFactory::getAllTransformTypes() +{ + if (m_transforms.empty()) populateTransforms(); + + std::set types; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + types.insert(i->second.type); + } + + std::vector rv; + for (std::set::iterator i = types.begin(); i != types.end(); ++i) { + rv.push_back(*i); + } + + return rv; +} + +std::vector +TransformFactory::getTransformCategories(QString transformType) +{ + if (m_transforms.empty()) populateTransforms(); + + std::set 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 rv; + for (std::set::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 +TransformFactory::getTransformMakers(QString transformType) +{ + if (m_transforms.empty()) populateTransforms(); + + std::set 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 rv; + for (std::set::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 names; + std::map pluginSources; + std::map 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 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 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(""); + + 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 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(""); + + 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(plugin)) || + (vp = dynamic_cast(plugin))) { + minChannels = vp->getMinChannelCount(); + maxChannels = vp->getMaxChannelCount(); + return true; + } else { + return getTransformChannelRange(identifier, minChannels, maxChannels); + } +} + +Model * +TransformFactory::getConfigurationForTransform(TransformId identifier, + const std::vector &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 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 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(inputModel)) { + sourceChannels = dynamic_cast(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(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. + } +} + diff -r 3ff8f571da09 -r 32e50b620a6c plugin/transform/TransformFactory.h --- /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 +#include + +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 TransformList; + + TransformList getAllTransforms(); + + std::vector getAllTransformTypes(); + + std::vector getTransformCategories(QString transformType); + std::vector 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 &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 TransformConfigurationMap; + TransformConfigurationMap m_lastConfigurations; + + typedef std::map TransformDescriptionMap; + TransformDescriptionMap m_transforms; + + typedef std::set 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