changeset 69:76cc2c424268

* Update the main sv.prf for compatibility with Qt 4.2 qmake instead of that from 4.1. Add a README.Qt41 describing how to build with 4.1 if preferred. * Add OSC support for control from external scripts etc (work in progress).
author Chris Cannam
date Fri, 10 Nov 2006 13:27:57 +0000 (2006-11-10)
parents 050d764df239
children e269ae6ed008
files audioio/PhaseVocoderTimeStretcher.cpp main/MainWindow.cpp main/MainWindow.h osc/OSCMessage.cpp osc/OSCMessage.h osc/OSCQueue.cpp osc/OSCQueue.h osc/sv-command osc/sv-osc-send.c sv.pro
diffstat 10 files changed, 902 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/PhaseVocoderTimeStretcher.cpp	Tue Oct 24 11:15:51 2006 +0000
+++ b/audioio/PhaseVocoderTimeStretcher.cpp	Fri Nov 10 13:27:57 2006 +0000
@@ -161,6 +161,10 @@
             if (m_wlen < 2048) m_wlen = 2048;
         }
         m_n1 = lrintf(m_n2 / m_ratio);
+        if (m_n1 == 0) {
+            m_n1 = 1;
+            m_n2 = m_ratio;
+        }
     }
 
     m_transientThreshold = lrintf(m_wlen / 4.5);
@@ -224,6 +228,9 @@
     size_t formerWlen = m_wlen;
     m_ratio = ratio;
 
+    std::cerr << "PhaseVocoderTimeStretcher::setRatio: new ratio " << ratio
+              << std::endl;
+
     calculateParameters();
 
     if (m_wlen == formerWlen) {
--- a/main/MainWindow.cpp	Tue Oct 24 11:15:51 2006 +0000
+++ b/main/MainWindow.cpp	Fri Nov 10 13:27:57 2006 +0000
@@ -54,6 +54,7 @@
 #include "base/CommandHistory.h"
 #include "base/Profiler.h"
 #include "base/Clipboard.h"
+#include "osc/OSCQueue.h"
 
 // For version information
 #include "vamp/vamp.h"
@@ -104,6 +105,7 @@
     m_audioOutput(withAudioOutput),
     m_playSource(0),
     m_playTarget(0),
+    m_oscQueue(new OSCQueue()),
     m_recentFiles("RecentFiles"),
     m_recentTransforms("RecentTransforms", 20),
     m_mainMenusCreated(false),
@@ -229,6 +231,13 @@
             this,
             SLOT(preferenceChanged(PropertyContainer::PropertyName)));
 
+    if (m_oscQueue->isOK()) {
+        connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC()));
+        QTimer *oscTimer = new QTimer(this);
+        connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC()));
+        oscTimer->start(1000);
+    }
+
     setupMenus();
     setupToolbars();
 
@@ -243,6 +252,7 @@
     delete m_playTarget;
     delete m_playSource;
     delete m_viewManager;
+    delete m_oscQueue;
     Profiles::getInstance()->dump();
 }
 
@@ -1268,6 +1278,8 @@
     action->setChecked(m_viewManager->getPlaySelectionMode());
     action->setShortcut(tr("s"));
     action->setStatusTip(tr("Constrain playback to the selected area"));
+    connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
+            action, SLOT(setChecked(bool)));
     connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
     connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool)));
 
@@ -1277,6 +1289,8 @@
     action->setChecked(m_viewManager->getPlayLoopMode());
     action->setShortcut(tr("l"));
     action->setStatusTip(tr("Loop playback"));
+    connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
+            action, SLOT(setChecked(bool)));
     connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
     connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool)));
 
@@ -2859,20 +2873,25 @@
 	return;
     }
 
-    CommandHistory::getInstance()->startCompoundOperation
-	(action->text(), true);
+    addPane(i->second, action->text());
+}
+
+void
+MainWindow::addPane(const PaneConfiguration &configuration, QString text)
+{
+    CommandHistory::getInstance()->startCompoundOperation(text, true);
 
     AddPaneCommand *command = new AddPaneCommand(this);
     CommandHistory::getInstance()->addCommand(command);
 
     Pane *pane = command->getPane();
 
-    if (i->second.layer == LayerFactory::Spectrum) {
+    if (configuration.layer == LayerFactory::Spectrum) {
         pane->setPlaybackFollow(View::PlaybackScrollContinuous);
     }
 
-    if (i->second.layer != LayerFactory::TimeRuler &&
-        i->second.layer != LayerFactory::Spectrum) {
+    if (configuration.layer != LayerFactory::TimeRuler &&
+        configuration.layer != LayerFactory::Spectrum) {
 
 	if (!m_timeRulerLayer) {
 //	    std::cerr << "no time ruler layer, creating one" << std::endl;
@@ -2885,9 +2904,9 @@
 	m_document->addLayerToView(pane, m_timeRulerLayer);
     }
 
-    Layer *newLayer = m_document->createLayer(i->second.layer);
-
-    Model *suggestedModel = i->second.sourceModel;
+    Layer *newLayer = m_document->createLayer(configuration.layer);
+
+    Model *suggestedModel = configuration.sourceModel;
     Model *model = 0;
 
     if (suggestedModel) {
@@ -2912,7 +2931,7 @@
 
     m_document->setModel(newLayer, model);
 
-    m_document->setChannel(newLayer, i->second.channel);
+    m_document->setChannel(newLayer, configuration.channel);
     m_document->addLayerToView(pane, newLayer);
 
     m_paneStack->setCurrentPane(pane);
@@ -3391,6 +3410,367 @@
 }
 
 void
+MainWindow::pollOSC()
+{
+    if (m_oscQueue->isEmpty()) return;
+    std::cerr << "MainWindow::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl;
+
+    OSCMessage message = m_oscQueue->readMessage();
+
+    if (message.getTarget() != 0) {
+        return; //!!! for now -- this class is target 0, others not handled yet
+    }
+
+    handleOSCMessage(message);
+}
+
+void
+MainWindow::handleOSCMessage(const OSCMessage &message)
+{
+    // This large function should really be abstracted out.
+
+    if (message.getMethod() == "open") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            QString path = message.getArg(0).toString();
+            if (!openSomeFile(path, ReplaceMainModel)) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << path.toStdString() << "\"" << std::endl;
+            }
+            //!!! we really need to spin here and not return until the
+            // file has been completely decoded...
+        }
+
+    } else if (message.getMethod() == "openadditional") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            QString path = message.getArg(0).toString();
+            if (!openSomeFile(path, CreateAdditionalModel)) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << path.toStdString() << "\"" << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "recent" ||
+               message.getMethod() == "last") {
+
+        int n = 0;
+        if (message.getMethod() == "recent" &&
+            message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::Int)) {
+            n = message.getArg(0).toInt() - 1;
+        }
+        std::vector<QString> recent = m_recentFiles.getRecent();
+        if (n >= 0 && n < recent.size()) {
+            if (!openSomeFile(recent[n], ReplaceMainModel)) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << recent[n].toStdString() << "\"" << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "save") {
+
+        QString path;
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            path = message.getArg(0).toString();
+            if (QFileInfo(path).exists()) {
+                std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in save" << std::endl;
+            } else {
+                saveSessionFile(path);
+            }
+        }
+
+    } else if (message.getMethod() == "export") {
+
+        QString path;
+        if (getMainModel()) {
+            if (message.getArgCount() == 1 &&
+                message.getArg(0).canConvert(QVariant::String)) {
+                path = message.getArg(0).toString();
+                if (QFileInfo(path).exists()) {
+                    std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in export" << std::endl;
+                } else {
+                    WavFileWriter writer(path,
+                                         getMainModel()->getSampleRate(),
+                                         getMainModel()->getChannelCount());
+                    MultiSelection ms = m_viewManager->getSelection();
+                    if (!ms.getSelections().empty()) {
+                        writer.writeModel(getMainModel(), &ms);
+                    } else {
+                        writer.writeModel(getMainModel());
+                    }
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "jump" ||
+               message.getMethod() == "play") {
+
+        if (getMainModel()) {
+
+            unsigned long frame = m_viewManager->getPlaybackFrame();
+            bool selection = false;
+            bool play = (message.getMethod() == "play");
+
+            if (message.getArgCount() == 1) {
+
+                if (message.getArg(0).canConvert(QVariant::String) &&
+                    message.getArg(0).toString() == "selection") {
+
+                    selection = true;
+
+                } else if (message.getArg(0).canConvert(QVariant::String) &&
+                           message.getArg(0).toString() == "end") {
+
+                    frame = getMainModel()->getEndFrame();
+
+                } else if (message.getArg(0).canConvert(QVariant::Double)) {
+
+                    double time = message.getArg(0).toDouble();
+                    if (time < 0.0) time = 0.0;
+
+                    frame = lrint(time * getMainModel()->getSampleRate());
+                }
+            }
+
+            if (frame > getMainModel()->getEndFrame()) {
+                frame = getMainModel()->getEndFrame();
+            }
+
+            if (play) {
+                m_viewManager->setPlaySelectionMode(selection);
+            } 
+
+            if (selection) {
+                MultiSelection::SelectionList sl = m_viewManager->getSelections();
+                if (!sl.empty()) {
+                    frame = sl.begin()->getStartFrame();
+                }
+            }
+
+            m_viewManager->setPlaybackFrame(frame);
+
+            if (play && !m_playSource->isPlaying()) {
+                m_playSource->play(frame);
+            }
+        }
+
+    } else if (message.getMethod() == "stop") {
+            
+        if (m_playSource->isPlaying()) m_playSource->stop();
+
+    } else if (message.getMethod() == "loop") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+
+            QString str = message.getArg(0).toString();
+            if (str == "on") {
+                m_viewManager->setPlayLoopMode(true);
+            } else if (str == "off") {
+                m_viewManager->setPlayLoopMode(false);
+            }
+        }
+
+    } else if (message.getMethod() == "select" ||
+               message.getMethod() == "addselect") {
+
+        if (getMainModel()) {
+
+            unsigned long f0 = getMainModel()->getStartFrame();
+            unsigned long f1 = getMainModel()->getEndFrame();
+
+            bool done = false;
+
+            if (message.getArgCount() == 2 &&
+                message.getArg(0).canConvert(QVariant::Double) &&
+                message.getArg(1).canConvert(QVariant::Double)) {
+                
+                double t0 = message.getArg(0).toDouble();
+                double t1 = message.getArg(1).toDouble();
+                if (t1 < t0) { double temp = t0; t0 = t1; t1 = temp; }
+                if (t0 < 0.0) t0 = 0.0;
+                if (t1 < 0.0) t1 = 0.0;
+
+                f0 = lrint(t0 * getMainModel()->getSampleRate());
+                f1 = lrint(t1 * getMainModel()->getSampleRate());
+
+            } else if (message.getArgCount() == 1 &&
+                       message.getArg(0).canConvert(QVariant::String)) {
+
+                QString str = message.getArg(0).toString();
+                if (str == "none") {
+                    m_viewManager->clearSelections();
+                    done = true;
+                }
+            }
+
+            if (!done) {
+                if (message.getMethod() == "select") {
+                    m_viewManager->setSelection(Selection(f0, f1));
+                } else {
+                    m_viewManager->addSelection(Selection(f0, f1));
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "add") {
+
+        if (getMainModel()) {
+
+            if (message.getArgCount() >= 1 &&
+                message.getArg(0).canConvert(QVariant::String)) {
+
+                int channel = -1;
+                if (message.getArgCount() == 2 &&
+                    message.getArg(0).canConvert(QVariant::Int)) {
+                    channel = message.getArg(0).toInt();
+                    if (channel < -1 ||
+                        channel > getMainModel()->getChannelCount()) {
+                        std::cerr << "WARNING: MainWindow::handleOSCMessage: channel "
+                                  << channel << " out of range" << std::endl;
+                        channel = -1;
+                    }
+                }
+
+                QString str = message.getArg(0).toString();
+                
+                LayerFactory::LayerType type =
+                    LayerFactory::getInstance()->getLayerTypeForName(str);
+
+                if (type == LayerFactory::UnknownLayer) {
+                    std::cerr << "WARNING: MainWindow::handleOSCMessage: unknown layer "
+                              << "type " << str.toStdString() << std::endl;
+                } else {
+
+                    PaneConfiguration configuration(type,
+                                                    getMainModel(),
+                                                    channel);
+                    
+                    addPane(configuration,
+                            tr("Add %1 Pane")
+                            .arg(LayerFactory::getInstance()->
+                                 getLayerPresentationName(type)));
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "undo") {
+
+        CommandHistory::getInstance()->undo();
+
+    } else if (message.getMethod() == "redo") {
+
+        CommandHistory::getInstance()->redo();
+
+    } else if (message.getMethod() == "set") {
+
+        if (message.getArgCount() >= 2 &&
+            message.getArg(0).canConvert(QVariant::String) &&
+            message.getArg(1).canConvert(QVariant::Double)) {
+
+            QString property = message.getArg(0).toString();
+            float value = (float)message.getArg(1).toDouble();
+
+            if (property == "gain") {
+                if (value < 0.0) value = 0.0;
+                m_fader->setValue(value);
+                if (m_playTarget) m_playTarget->setOutputGain(value);
+            } else if (property == "speedup") {
+                m_playSpeed->setMappedValue(value);
+            } else if (property == "overlays") {
+                if (value < 0.5) {
+                    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
+                } else if (value < 1.5) {
+                    m_viewManager->setOverlayMode(ViewManager::BasicOverlays);
+                } else {
+                    m_viewManager->setOverlayMode(ViewManager::AllOverlays);
+                }                    
+            }
+        }
+
+    } else if (message.getMethod() == "setcurrent") {
+
+        int paneIndex = -1, layerIndex = -1;
+        bool wantLayer = false;
+
+        if (message.getArgCount() >= 1 &&
+            message.getArg(0).canConvert(QVariant::Int)) {
+
+            paneIndex = message.getArg(0).toInt() - 1;
+
+            if (message.getArgCount() >= 2 &&
+                message.getArg(1).canConvert(QVariant::Int)) {
+                wantLayer = true;
+                layerIndex = message.getArg(1).toInt() - 1;
+            }
+        }
+
+        if (paneIndex >= 0 && paneIndex < m_paneStack->getPaneCount()) {
+            Pane *pane = m_paneStack->getPane(paneIndex);
+            if (layerIndex >= 0 && layerIndex < pane->getLayerCount()) {
+                Layer *layer = pane->getLayer(layerIndex);
+                m_paneStack->setCurrentLayer(pane, layer);
+            } else if (wantLayer && layerIndex == -1) {
+                m_paneStack->setCurrentLayer(pane, 0);
+            } else {
+                m_paneStack->setCurrentPane(pane);
+            }
+        }
+
+    } else if (message.getMethod() == "delete") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            
+            QString target = message.getArg(0).toString();
+
+            if (target == "pane") {
+
+                deleteCurrentPane();
+
+            } else if (target == "layer") {
+
+                deleteCurrentLayer();
+
+            } else {
+                
+                std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown delete target " << target.toStdString() << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "zoom") {
+
+        if (message.getArgCount() == 1) {
+            if (message.getArg(0).canConvert(QVariant::String) &&
+                message.getArg(0).toString() == "in") {
+                zoomIn();
+            } else if (message.getArg(0).canConvert(QVariant::String) &&
+                       message.getArg(0).toString() == "out") {
+                zoomOut();
+            } else if (message.getArg(0).canConvert(QVariant::String) &&
+                       message.getArg(0).toString() == "default") {
+                zoomDefault();
+            } else if (message.getArg(0).canConvert(QVariant::Double)) {
+                double level = message.getArg(0).toDouble();
+                Pane *currentPane = m_paneStack->getCurrentPane();
+                if (level < 1.0) level = 1.0;
+                if (currentPane) currentPane->setZoomLevel(lrint(level));
+            }
+        }
+
+    } else {
+        std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown or unsupported "
+                  << "method \"" << message.getMethod().toStdString()
+                  << "\"" << std::endl;
+    }
+            
+}
+
+void
 MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
 {
     if (name == "Property Box Layout") {
@@ -3537,6 +3917,12 @@
 #endif
     aboutText += tr("<br>With LADSPA plugin support (API v%1) &copy; Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION);
     aboutText += tr("<br>With DSSI plugin support (API v%1) &copy; Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION);
+#ifdef HAVE_LIBLO
+    aboutText += tr("<br>With liblo Lite OSC library (v%1) &copy; Steve Harris").arg(LIBLO_VERSION);
+    if (m_oscQueue->isOK()) {
+        aboutText += tr("<p>The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL());
+    }
+#endif
     aboutText += "</p>";
 #endif
 
--- a/main/MainWindow.h	Tue Oct 24 11:15:51 2006 +0000
+++ b/main/MainWindow.h	Fri Nov 10 13:27:57 2006 +0000
@@ -48,6 +48,8 @@
 class QCheckBox;
 class PreferencesDialog;
 class QPushButton;
+class OSCQueue;
+class OSCMessage;
 
 
 class MainWindow : public QMainWindow
@@ -195,6 +197,9 @@
 
     void showLayerTree();
 
+    void pollOSC();
+    void handleOSCMessage(const OSCMessage &);
+
     void website();
     void help();
     void about();
@@ -219,6 +224,8 @@
     AudioCallbackPlaySource *m_playSource;
     AudioCallbackPlayTarget *m_playTarget;
 
+    OSCQueue                *m_oscQueue;
+
     RecentFiles              m_recentFiles;
     RecentFiles              m_recentTransforms;
 
@@ -281,6 +288,8 @@
 
     Pane *addPaneToStack();
 
+    void addPane(const PaneConfiguration &configuration, QString text);
+
     class PaneCallback : public SVFileReaderPaneCallback
     {
     public:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/osc/OSCMessage.cpp	Fri Nov 10 13:27:57 2006 +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/osc/OSCMessage.h	Fri Nov 10 13:27:57 2006 +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/osc/OSCQueue.cpp	Fri Nov 10 13:27:57 2006 +0000
@@ -0,0 +1,215 @@
+/* -*- 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 "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;
+        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;
+        sleep(1);
+        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/osc/OSCQueue.h	Fri Nov 10 13:27:57 2006 +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.
+*/
+
+#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/osc/sv-command	Fri Nov 10 13:27:57 2006 +0000
@@ -0,0 +1,17 @@
+#!/bin/sh
+port=`lsof -c sonic- | grep UDP | sed -e 's/^.*[^0-9]\([0-9][0-9]*\) *$/\1/' | grep -v ' ' | head -1`
+if [ -z "$port" ]; then
+    echo "Sonic Visualiser OSC port not found"
+    exit 1
+fi
+if [ -n "$1" ]; then
+    command=$1; shift
+    echo "osc.udp://127.0.0.1:$port/$command" "$@"
+    sv-osc-send "osc.udp://127.0.0.1:$port/$command" "$@"
+else
+    while read command arg1 arg2 arg3 arg4 arg5; do
+	echo "osc.udp://127.0.0.1:$port/$command" $arg1 $arg2 $arg3 $arg4 $arg5
+	sv-osc-send "osc.udp://127.0.0.1:$port/$command" $arg1 $arg2 $arg3 $arg4 $arg5
+    done
+fi
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/osc/sv-osc-send.c	Fri Nov 10 13:27:57 2006 +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/sv.pro	Tue Oct 24 11:15:51 2006 +0000
+++ b/sv.pro	Fri Nov 10 13:27:57 2006 +0000
@@ -1,7 +1,7 @@
 
 TEMPLATE = app
 
-SV_UNIT_PACKAGES = vamp vamp-sdk fftw3f samplerate jack portaudio mad oggz fishsound lrdf raptor sndfile
+SV_UNIT_PACKAGES = vamp vamp-sdk fftw3f samplerate jack portaudio mad oggz fishsound lrdf raptor sndfile liblo
 load(../sv.prf)
 
 CONFIG += sv qt thread warn_on stl rtti exceptions
@@ -9,8 +9,8 @@
 
 TARGET = sonic-visualiser
 
-DEPENDPATH += . .. audioio document i18n main transform
-INCLUDEPATH += . .. audioio document transform main
+DEPENDPATH += . .. audioio document i18n main osc transform
+INCLUDEPATH += . .. audioio document transform osc main
 LIBPATH = ../view ../layer ../data ../widgets ../plugin ../base ../system $$LIBPATH
 
 contains(DEFINES, BUILD_STATIC):LIBS -= -ljack
@@ -42,6 +42,8 @@
            document/SVFileReader.h \
            main/MainWindow.h \
            main/PreferencesDialog.h \
+           osc/OSCMessage.h \
+           osc/OSCQueue.h \
            transform/FeatureExtractionPluginTransform.h \
            transform/PluginTransform.h \
            transform/RealTimePluginTransform.h \
@@ -61,6 +63,8 @@
            main/main.cpp \
            main/MainWindow.cpp \
            main/PreferencesDialog.cpp \
+           osc/OSCMessage.cpp \
+           osc/OSCQueue.cpp \
            transform/FeatureExtractionPluginTransform.cpp \
            transform/PluginTransform.cpp \
            transform/RealTimePluginTransform.cpp \