# HG changeset patch # User Chris Cannam # Date 1163165277 0 # Node ID 76cc2c42426889f907d6263bcc75a1d9b621d77a # Parent 050d764df23995b6f5f73b72c9bdcc93a4c3d6e8 * 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). diff -r 050d764df239 -r 76cc2c424268 audioio/PhaseVocoderTimeStretcher.cpp --- 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) { diff -r 050d764df239 -r 76cc2c424268 main/MainWindow.cpp --- 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 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("
With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); aboutText += tr("
With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); +#ifdef HAVE_LIBLO + aboutText += tr("
With liblo Lite OSC library (v%1) © Steve Harris").arg(LIBLO_VERSION); + if (m_oscQueue->isOK()) { + aboutText += tr("

The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL()); + } +#endif aboutText += "

"; #endif diff -r 050d764df239 -r 76cc2c424268 main/MainWindow.h --- 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: diff -r 050d764df239 -r 76cc2c424268 osc/OSCMessage.cpp --- /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]; +} + diff -r 050d764df239 -r 76cc2c424268 osc/OSCMessage.h --- /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 +#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 050d764df239 -r 76cc2c424268 osc/OSCQueue.cpp --- /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 + +#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; + 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; +} + diff -r 050d764df239 -r 76cc2c424268 osc/OSCQueue.h --- /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 + +#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 050d764df239 -r 76cc2c424268 osc/sv-command --- /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 + diff -r 050d764df239 -r 76cc2c424268 osc/sv-osc-send.c --- /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 +#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 050d764df239 -r 76cc2c424268 sv.pro --- 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 \