summaryrefslogtreecommitdiffstats
path: root/src/osc
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-10-21 23:44:02 +0000
committerDavid Robillard <d@drobilla.net>2011-10-21 23:44:02 +0000
commitc7b05953949dbc80eee22348270166c2a6704a95 (patch)
treed0ac1b757831bd5b4944d008101e68054e9f1c37 /src/osc
parent5a413b8cd31006c8836bcc7409f7055c890f8a68 (diff)
downloadingen-c7b05953949dbc80eee22348270166c2a6704a95.tar.gz
ingen-c7b05953949dbc80eee22348270166c2a6704a95.tar.bz2
ingen-c7b05953949dbc80eee22348270166c2a6704a95.zip
Move engine side OSC and HTTP stuff to separate modules.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@3576 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/osc')
-rw-r--r--src/osc/OSCClientSender.cpp269
-rw-r--r--src/osc/OSCClientSender.hpp110
-rw-r--r--src/osc/OSCEngineReceiver.cpp588
-rw-r--r--src/osc/OSCEngineReceiver.hpp130
-rw-r--r--src/osc/ingen_osc.cpp53
5 files changed, 1150 insertions, 0 deletions
diff --git a/src/osc/OSCClientSender.cpp b/src/osc/OSCClientSender.cpp
new file mode 100644
index 00000000..ba098a61
--- /dev/null
+++ b/src/osc/OSCClientSender.cpp
@@ -0,0 +1,269 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen 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.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <unistd.h>
+
+#include <cassert>
+#include <string>
+
+#include "raul/log.hpp"
+#include "raul/AtomLiblo.hpp"
+#include "raul/Path.hpp"
+
+#include "ingen/ClientInterface.hpp"
+
+#include "OSCClientSender.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Server {
+
+/** @page client_osc_namespace Client OSC Namespace Documentation
+ *
+ * <p>These are the commands the client recognizes. All monitoring of
+ * changes in the engine happens via these commands.</p>
+ */
+
+/** @page client_osc_namespace
+ * <h2>/ok</h2>
+ * @arg @p response-id :: Integer
+ *
+ * @par
+ * Successful response to some command.
+ */
+void
+OSCClientSender::response_ok(int32_t id)
+{
+ if (!_enabled)
+ return;
+
+ if (lo_send(_address, "/ok", "i", id, LO_ARGS_END) < 0) {
+ Raul::error << "Unable to send OK " << id << "! ("
+ << lo_address_errstr(_address) << ")" << endl;
+ }
+}
+
+/** @page client_osc_namespace
+ * <h2>/error</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p message :: String
+ *
+ * @par
+ * Unsuccessful response to some command.
+ */
+void
+OSCClientSender::response_error(int32_t id, const std::string& msg)
+{
+ if (!_enabled)
+ return;
+
+ if (lo_send(_address, "/error", "is", id, msg.c_str(), LO_ARGS_END) < 0) {
+ Raul::error << "Unable to send error " << id << "! ("
+ << lo_address_errstr(_address) << ")" << endl;
+ }
+}
+
+/** @page client_osc_namespace
+ * <h2>/error</h2>
+ * @arg @p message :: String
+ *
+ * @par
+ * Notification that an error has occurred. This is for notification of errors
+ * that aren't a direct response to a user command, i.e. "unexpected" errors.
+ */
+void
+OSCClientSender::error(const std::string& msg)
+{
+ send("/error", "s", msg.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/put</h2>
+ * @arg @p path :: String
+ * @arg @p predicate :: URI String
+ * @arg @p value
+ * @arg @p ...
+ *
+ * @par
+ * PUT a set of properties to a path.
+ */
+void
+OSCClientSender::put(const Raul::URI& path,
+ const Resource::Properties& properties,
+ Resource::Graph ctx)
+{
+ typedef Resource::Properties::const_iterator iterator;
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, path.c_str());
+ lo_message_add_string(m, Resource::graph_to_uri(ctx).c_str());
+ for (iterator i = properties.begin(); i != properties.end(); ++i) {
+ lo_message_add_string(m, i->first.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, i->second);
+ }
+ send_message("/put", m);
+}
+
+void
+OSCClientSender::delta(const Raul::URI& path,
+ const Resource::Properties& remove,
+ const Resource::Properties& add)
+{
+ typedef Resource::Properties::const_iterator iterator;
+
+ const bool bundle = !_bundle;
+ if (bundle)
+ bundle_begin();
+
+ send("/delta_begin", "s", path.c_str(), LO_ARGS_END);
+
+ for (iterator i = remove.begin(); i != remove.end(); ++i) {
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, i->first.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, i->second);
+ send_message("/delta_remove", m);
+ }
+
+ for (iterator i = add.begin(); i != add.end(); ++i) {
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, i->first.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, i->second);
+ send_message("/delta_add", m);
+ }
+
+ send("/delta_end", "", LO_ARGS_END);
+
+ if (bundle)
+ bundle_end();
+}
+
+/** @page client_osc_namespace
+ * <h2>/move</h2>
+ * @arg @p old-path :: String
+ * @arg @p new-path :: String
+ *
+ * @par
+ * MOVE an object to a new path.
+ * The new path will have the same parent as the old path.
+ */
+void
+OSCClientSender::move(const Path& old_path, const Path& new_path)
+{
+ send("/move", "ss", old_path.c_str(), new_path.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/delete</h2>
+ * @arg @p path :: String
+ *
+ * @par
+ * DELETE an object.
+ */
+void
+OSCClientSender::del(const URI& uri)
+{
+ send("/delete", "s", uri.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/connect</h2>
+ * @arg @p src-path :: String
+ * @arg @p dst-path :: String
+ *
+ * @par
+ * Notification a new connection has been made.
+ */
+void
+OSCClientSender::connect(const Path& src_port_path,
+ const Path& dst_port_path)
+{
+ send("/connect", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/disconnect</h2>
+ * @arg @p src-path :: String
+ * @arg @p dst-path :: String
+ *
+ * @par
+ * Notification a connection has been unmade.
+ */
+void
+OSCClientSender::disconnect(const URI& src,
+ const URI& dst)
+{
+ send("/disconnect", "ss", src.c_str(), dst.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/disconnect_all</h2>
+ * @arg @p parent-patch-path :: String
+ * @arg @p path :: String
+ *
+ * @par
+ * Notification all connections to an object have been disconnected.
+ */
+void
+OSCClientSender::disconnect_all(const Raul::Path& parent_patch_path,
+ const Raul::Path& path)
+{
+ send("/disconnect_all", "ss", parent_patch_path.c_str(), path.c_str(), LO_ARGS_END);
+}
+
+/** @page client_osc_namespace
+ * <h2>/set_property</h2>
+ * @arg @p path :: String
+ * @arg @p key :: URI String
+ * @arg @p value
+ *
+ * @par
+ * Notification of a property.
+ */
+void
+OSCClientSender::set_property(const URI& path,
+ const URI& key,
+ const Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, path.c_str());
+ lo_message_add_string(m, key.c_str());
+ AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/set_property", m);
+}
+
+/** @page client_osc_namespace
+ * <h2>/activity</h2>
+ * @arg @p path :: String
+ *
+ * @par
+ * Notification of "activity" (e.g. port message blinkenlights).
+ */
+void
+OSCClientSender::activity(const Path& path, const Raul::Atom& value)
+{
+ if (!_enabled)
+ return;
+
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, path.c_str());
+ AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/activity", m);
+}
+
+} // namespace Server
+} // namespace Ingen
diff --git a/src/osc/OSCClientSender.hpp b/src/osc/OSCClientSender.hpp
new file mode 100644
index 00000000..ffc3b202
--- /dev/null
+++ b/src/osc/OSCClientSender.hpp
@@ -0,0 +1,110 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen 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.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef INGEN_ENGINE_OSCCLIENTSENDER_HPP
+#define INGEN_ENGINE_OSCCLIENTSENDER_HPP
+
+#include <cassert>
+#include <string>
+
+#include <lo/lo.h>
+
+#include "ingen/ClientInterface.hpp"
+#include "ingen/GraphObject.hpp"
+#include "../shared/OSCSender.hpp"
+
+namespace Ingen {
+
+class ServerInterface;
+
+namespace Server {
+
+
+/** Implements ClientInterface for OSC clients (sends OSC messages).
+ *
+ * \ingroup engine
+ */
+class OSCClientSender : public ClientInterface,
+ public Ingen::Shared::OSCSender
+{
+public:
+ explicit OSCClientSender(const Raul::URI& url,
+ size_t max_packet_size)
+ : Shared::OSCSender(max_packet_size)
+ , _url(url)
+ {
+ _address = lo_address_new_from_url(url.c_str());
+ }
+
+ virtual ~OSCClientSender()
+ { lo_address_free(_address); }
+
+ bool enabled() const { return _enabled; }
+
+ void enable() { _enabled = true; }
+ void disable() { _enabled = false; }
+
+ void bundle_begin() { OSCSender::bundle_begin(); }
+ void bundle_end() { OSCSender::bundle_end(); }
+
+ Raul::URI uri() const { return _url; }
+
+ /* *** ClientInterface Implementation Below *** */
+
+ void response_ok(int32_t id);
+ void response_error(int32_t id, const std::string& msg);
+
+ void error(const std::string& msg);
+
+ virtual void put(const Raul::URI& path,
+ const Resource::Properties& properties,
+ Resource::Graph ctx=Resource::DEFAULT);
+
+ virtual void delta(const Raul::URI& path,
+ const Resource::Properties& remove,
+ const Resource::Properties& add);
+
+ virtual void del(const Raul::URI& uri);
+
+ virtual void move(const Raul::Path& old_path,
+ const Raul::Path& new_path);
+
+ virtual void connect(const Raul::Path& src_port_path,
+ const Raul::Path& dst_port_path);
+
+ virtual void disconnect(const Raul::URI& src,
+ const Raul::URI& dst);
+
+ virtual void disconnect_all(const Raul::Path& parent_patch_path,
+ const Raul::Path& path);
+
+ virtual void set_property(const Raul::URI& subject,
+ const Raul::URI& predicate,
+ const Raul::Atom& value);
+
+ virtual void activity(const Raul::Path& path,
+ const Raul::Atom& value);
+
+private:
+ Raul::URI _url;
+};
+
+} // namespace Server
+} // namespace Ingen
+
+#endif // INGEN_ENGINE_OSCCLIENTSENDER_HPP
+
diff --git a/src/osc/OSCEngineReceiver.cpp b/src/osc/OSCEngineReceiver.cpp
new file mode 100644
index 00000000..254cc850
--- /dev/null
+++ b/src/osc/OSCEngineReceiver.cpp
@@ -0,0 +1,588 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen 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.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string>
+
+#include <lo/lo.h>
+
+#include "raul/AtomLiblo.hpp"
+#include "raul/Path.hpp"
+#include "raul/SharedPtr.hpp"
+#include "raul/log.hpp"
+
+#include "ingen-config.h"
+#include "ingen/ClientInterface.hpp"
+#include "ingen/ServerInterface.hpp"
+
+#include "../server/ClientBroadcaster.hpp"
+#include "../server/Engine.hpp"
+
+#include "OSCClientSender.hpp"
+#include "OSCEngineReceiver.hpp"
+
+#define LOG(s) s << "[OSCEngineReceiver] "
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Server {
+
+/** @page engine_osc_namespace Server OSC Namespace Documentation
+ *
+ * <p>These are the commands the engine recognizes. A client can control every
+ * aspect of the engine entirely with these commands.</p>
+ *
+ * <p>All commands on this page are in the "control band". If a client needs to
+ * know about the state of the engine, it must listen to the "notification band".
+ * See the "Client OSC Namespace Documentation" for details.</p>
+ */
+
+OSCEngineReceiver::OSCEngineReceiver(Engine& engine,
+ SharedPtr<ServerInterface> interface,
+ uint16_t port)
+ : _engine(engine)
+ , _interface(interface)
+ , _server(NULL)
+{
+ _receive_thread = new ReceiveThread(*this);
+
+ char port_str[6];
+ snprintf(port_str, sizeof(port_str), "%u", port);
+
+ _server = lo_server_new(port_str, error_cb);
+
+ if (_server == NULL) {
+ LOG(error) << "Could not start OSC server. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ char* lo_url = lo_server_get_url(_server);
+ LOG(info) << "Started OSC server at " << lo_url << endl;
+ free(lo_url);
+ }
+
+#ifdef RAUL_LOG_DEBUG
+ lo_server_add_method(_server, NULL, NULL, generic_cb, NULL);
+#endif
+
+ // Set response address for this message.
+ // It's important this is first and returns nonzero.
+ lo_server_add_method(_server, NULL, NULL, set_response_address_cb, this);
+
+#ifdef LIBLO_BUNDLES
+ lo_server_add_bundle_handlers(_server, bundle_start_cb, bundle_end_cb, this);
+#endif
+
+ // Commands
+ lo_server_add_method(_server, "/ping", "i", ping_cb, this);
+ lo_server_add_method(_server, "/ping_queued", "i", ping_slow_cb, this);
+ lo_server_add_method(_server, "/register_client", "i", register_client_cb, this);
+ lo_server_add_method(_server, "/unregister_client", "i", unregister_client_cb, this);
+ lo_server_add_method(_server, "/put", NULL, put_cb, this);
+ lo_server_add_method(_server, "/delta_begin", NULL, delta_begin_cb, this);
+ lo_server_add_method(_server, "/delta_remove", NULL, delta_remove_cb, this);
+ lo_server_add_method(_server, "/delta_add", NULL, delta_add_cb, this);
+ lo_server_add_method(_server, "/delta_end", NULL, delta_end_cb, this);
+ lo_server_add_method(_server, "/move", "iss", move_cb, this);
+ lo_server_add_method(_server, "/delete", "is", del_cb, this);
+ lo_server_add_method(_server, "/connect", "iss", connect_cb, this);
+ lo_server_add_method(_server, "/disconnect", "iss", disconnect_cb, this);
+ lo_server_add_method(_server, "/disconnect_all", "iss", disconnect_all_cb, this);
+ lo_server_add_method(_server, "/note_on", "isii", note_on_cb, this);
+ lo_server_add_method(_server, "/note_off", "isi", note_off_cb, this);
+ lo_server_add_method(_server, "/all_notes_off", "isi", all_notes_off_cb, this);
+ lo_server_add_method(_server, "/learn", "is", learn_cb, this);
+ lo_server_add_method(_server, "/set_property", NULL, set_property_cb, this);
+
+ // Queries
+ lo_server_add_method(_server, "/get", "is", get_cb, this);
+
+ lo_server_add_method(_server, NULL, NULL, unknown_cb, NULL);
+
+ _receive_thread->set_name("OSCEngineReceiver Listener");
+ _receive_thread->start();
+ _receive_thread->set_scheduling(SCHED_FIFO, 5);
+}
+
+OSCEngineReceiver::~OSCEngineReceiver()
+{
+ _receive_thread->stop();
+ delete _receive_thread;
+
+ if (_server != NULL) {
+ lo_server_free(_server);
+ _server = NULL;
+ }
+}
+
+/** Override the semaphore driven _run method of ServerInterfaceImpl
+ * to wait on OSC messages and prepare them right away in the same thread.
+ */
+void
+OSCEngineReceiver::ReceiveThread::_run()
+{
+ /* get a timestamp here and stamp all the events with the same time so
+ * they all get executed in the same cycle */
+
+ while (true) {
+ // Wait on a message and enqueue it
+ lo_server_recv(_receiver._server);
+
+ // Enqueue every other message that is here "now"
+ // (would this provide truly atomic bundles?)
+ while (lo_server_recv_noblock(_receiver._server, 0) > 0) {}
+ }
+}
+
+/** Create a new request for this message, if necessary.
+ *
+ * This is based on the fact that the current request is stored in a ref
+ * counted pointer, and events just take a reference to that. Thus, events
+ * may delete their request if we've since switched to a new one, or the
+ * same one can stay around and serve a series of events.
+ * Hooray for reference counting.
+ *
+ * If this message came from the same source as the last message, no allocation
+ * of requests or lo_addresses or any of it needs to be done. Unfortunately
+ * the only way to check is by comparing URLs, because liblo addresses suck.
+ * Lack of a fast liblo address comparison really sucks here, in any case.
+ */
+int
+OSCEngineReceiver::set_response_address_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data)
+{
+ OSCEngineReceiver* const me = reinterpret_cast<OSCEngineReceiver*>(user_data);
+
+ if (argc < 1 || types[0] != 'i') // Not a valid Ingen message
+ return 0; // Save liblo the trouble
+
+ const int32_t id = argv[0]->i;
+
+ const lo_address addr = lo_message_get_source(msg);
+ char* const url = lo_address_get_url(addr);
+
+ if (id != -1) {
+ // TODO: Cache client
+ ClientInterface* client = me->_engine.broadcaster()->client(url);
+ me->_interface->respond_to(client, id);
+ } else {
+ me->_interface->disable_responses();
+ }
+
+ free(url);
+
+ // If this returns 0 no OSC commands will work
+ return 1;
+}
+
+#ifdef LIBLO_BUNDLES
+int
+OSCEngineReceiver::_bundle_start_cb(lo_timetag time)
+{
+ info << "BUNDLE START" << endl;
+ return 0;
+}
+
+int
+OSCEngineReceiver::_bundle_end_cb()
+{
+ info << "BUNDLE END" << endl;
+ return 0;
+}
+#endif
+
+void
+OSCEngineReceiver::error_cb(int num, const char* msg, const char* path)
+{
+ error << "liblo server error" << num;
+ if (path) {
+ error << " for path `" << path << "'";
+ }
+ error << " (" << msg << ")" << endl;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/ping</h2>
+ * @arg @p response-id :: Integer
+ */
+int
+OSCEngineReceiver::_ping_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const lo_address addr = lo_message_get_source(msg);
+ if (lo_send(addr, "/ok", "i", argv[0]->i) < 0)
+ warn << "Unable to send response (" << lo_address_errstr(addr) << ")" << endl;
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/ping_queued</h2>
+ * @arg @p response-id :: Integer
+ *
+ * @par
+ * Reply to sender with a successful response after going through the
+ * event queue. This is useful for checking if the engine is actually active,
+ * or for sending after several events as a sentinel and wait on it's response,
+ * to know when all previous events have finished processing.
+ */
+int
+OSCEngineReceiver::_ping_slow_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _interface->ping();
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/register_client</h2>
+ * @arg @p response-id :: Integer
+ *
+ * @par
+ * Register a new client with the engine. The incoming address will be
+ * used for the new registered client.
+ */
+int
+OSCEngineReceiver::_register_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ lo_address addr = lo_message_get_source(msg);
+
+ char* const url = lo_address_get_url(addr);
+ ClientInterface* client = new OSCClientSender(
+ (const char*)url,
+ _engine.world()->conf()->option("packet-size").get_int32());
+ _interface->register_client(client);
+ free(url);
+
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/unregister_client</h2>
+ * @arg @p response-id :: Integer
+ *
+ * @par
+ * Unregister a client.
+ */
+int
+OSCEngineReceiver::_unregister_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ lo_address addr = lo_message_get_source(msg);
+
+ char* url = lo_address_get_url(addr);
+ _interface->unregister_client(url);
+ free(url);
+
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/get</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p uri :: URI String
+ *
+ * @par
+ * Request all properties of an object.
+ */
+int
+OSCEngineReceiver::_get_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _interface->get(&argv[1]->s);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/put</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p path :: String
+ * @arg @p context :: URI String
+ * @arg @p predicate :: URI String
+ * @arg @p value
+ * @arg @p ...
+ *
+ * @par
+ * PUT a set of properties to a path.
+ */
+int
+OSCEngineReceiver::_put_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* obj_path = &argv[1]->s;
+ const char* ctx = &argv[2]->s;
+ Resource::Properties prop;
+ for (int i = 3; i < argc-1; i += 2)
+ prop.insert(make_pair(&argv[i]->s,
+ AtomLiblo::lo_arg_to_atom(types[i+1], argv[i+1])));
+ _interface->put(obj_path, prop, Resource::uri_to_graph(ctx));
+ return 0;
+}
+
+int
+OSCEngineReceiver::_delta_begin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* obj_path = &argv[1]->s;
+ assert(_delta_remove.empty());
+ assert(_delta_add.empty());
+ _delta_uri = obj_path;
+ return 0;
+}
+
+int
+OSCEngineReceiver::_delta_remove_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _delta_remove.insert(make_pair(&argv[1]->s,
+ AtomLiblo::lo_arg_to_atom(types[2], argv[2])));
+ return 0;
+}
+
+int
+OSCEngineReceiver::_delta_add_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _delta_add.insert(make_pair(&argv[1]->s,
+ AtomLiblo::lo_arg_to_atom(types[2], argv[2])));
+ return 0;
+}
+
+int
+OSCEngineReceiver::_delta_end_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _interface->delta(_delta_uri, _delta_remove, _delta_add);
+ _delta_uri = Raul::URI();
+ _delta_remove.clear();
+ _delta_add.clear();
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/move</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p old-path :: String
+ * @arg @p new-path :: String
+ *
+ * @par
+ * MOVE an object to a new path.
+ */
+int
+OSCEngineReceiver::_move_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* old_path = &argv[1]->s;
+ const char* new_path = &argv[2]->s;
+
+ _interface->move(old_path, new_path);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/delete</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p path :: String
+ *
+ * @par
+ * DELETE an object.
+ */
+int
+OSCEngineReceiver::_del_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* uri = &argv[1]->s;
+
+ _interface->del(uri);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/connect</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p src-port-path :: String
+ * @arg @p dst-port-path :: String
+ *
+ * @par
+ * Connect two ports (which must be in the same patch).
+ */
+int
+OSCEngineReceiver::_connect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* src_port_path = &argv[1]->s;
+ const char* dst_port_path = &argv[2]->s;
+
+ _interface->connect(src_port_path, dst_port_path);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/disconnect</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p src-port-path :: String
+ * @arg @p dst-port-path :: String
+ *
+ * @par
+ * Disconnect two ports.
+ */
+int
+OSCEngineReceiver::_disconnect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* src_port_path = &argv[1]->s;
+ const char* dst_port_path = &argv[2]->s;
+
+ _interface->disconnect(src_port_path, dst_port_path);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/disconnect_all</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p patch-path :: String
+ * @arg @p object-path :: String
+ *
+ * @par
+ * Disconnect all connections to/from a node/port.
+ */
+int
+OSCEngineReceiver::_disconnect_all_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* patch_path = &argv[1]->s;
+ const char* object_path = &argv[2]->s;
+
+ _interface->disconnect_all(patch_path, object_path);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/note_on</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p node-path :: String
+ * @arg @p note-num (int)
+ * @arg @p velocity (int)
+ *
+ * @par
+ * Trigger a note-on, just as if it came from MIDI.
+ */
+int
+OSCEngineReceiver::_note_on_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ /*
+ const char* node_path = &argv[1]->s;
+ const uint8_t note_num = argv[2]->i;
+ const uint8_t velocity = argv[3]->i;
+ */
+ warn << "TODO: OSC note on" << endl;
+ //note_on(node_path, note_num, velocity);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/note_off</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p node-path :: String
+ * @arg @p note-num :: Integer
+ *
+ * @par
+ * Trigger a note-off, just as if it came from MIDI.
+ */
+int
+OSCEngineReceiver::_note_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ /*
+ const char* patch_path = &argv[1]->s;
+ const uint8_t note_num = argv[2]->i;
+ */
+ warn << "TODO: OSC note off" << endl;
+ //note_off(patch_path, note_num);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/all_notes_off</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p patch-path :: String
+ *
+ * @par
+ * Trigger a note-off for all voices, just as if it came from MIDI.
+ */
+int
+OSCEngineReceiver::_all_notes_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ /*
+
+ const char* patch_path = &argv[1]->s;
+ */
+ warn << "TODO: OSC all notes off" << endl;
+ //all_notes_off(patch_path);
+ return 0;
+}
+
+/** @page engine_osc_namespace
+ * <h2>/set_property</h2>
+ * @arg @p response-id :: Integer
+ * @arg @p uri :: URI String
+ * @arg @p key :: URI String
+ * @arg @p value :: String
+ *
+ * @par
+ * Set a property on a graph object.
+ */
+int
+OSCEngineReceiver::_set_property_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ if (argc != 4 || types[0] != 'i' || types[1] != 's' || types[2] != 's')
+ return 1;
+
+ const char* object_path = &argv[1]->s;
+ const char* key = &argv[2]->s;
+
+ Raul::Atom value = Raul::AtomLiblo::lo_arg_to_atom(types[3], argv[3]);
+
+ _interface->set_property(object_path, key, value);
+ return 0;
+}
+
+
+// Static Callbacks //
+
+
+// Display incoming OSC messages (for debugging purposes)
+int
+OSCEngineReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data)
+{
+ printf("[OSCEngineReceiver] %s (%s)\t", path, types);
+
+ for (int i=0; i < argc; ++i) {
+ lo_arg_pp(lo_type(types[i]), argv[i]);
+ printf("\t");
+ }
+ printf("\n");
+
+ return 1; // not handled
+}
+
+int
+OSCEngineReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data)
+{
+ const lo_address addr = lo_message_get_source(msg);
+ char* const url = lo_address_get_url(addr);
+
+ warn << "Unknown OSC command " << path << " (" << types << ") "
+ << "received from " << url << endl;
+
+ string error_msg = "Unknown command: ";
+ error_msg.append(path).append(" ").append(types);
+
+ lo_send(addr, "/error", "s", error_msg.c_str(), LO_ARGS_END);
+ free(url);
+
+ return 0;
+}
+
+} // namespace Server
+} // namespace Ingen
diff --git a/src/osc/OSCEngineReceiver.hpp b/src/osc/OSCEngineReceiver.hpp
new file mode 100644
index 00000000..fe16ac1a
--- /dev/null
+++ b/src/osc/OSCEngineReceiver.hpp
@@ -0,0 +1,130 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen 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.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef INGEN_ENGINE_OSCENGINERECEIVER_HPP
+#define INGEN_ENGINE_OSCENGINERECEIVER_HPP
+
+#include <stdint.h>
+
+#include <lo/lo.h>
+
+#include "raul/Thread.hpp"
+#include "raul/URI.hpp"
+
+#include "ingen/Resource.hpp"
+
+#include "ingen-config.h"
+
+namespace Ingen {
+
+class ServerInterface;
+
+namespace Server {
+
+class Engine;
+
+/* Some boilerplate killing macros... */
+#define LO_HANDLER_ARGS const char* path, const char* types, lo_arg** argv, int argc, lo_message msg
+
+/* Defines a static handler to be passed to lo_add_method, which is a trivial
+ * wrapper around a non-static method that does the real work. Makes a whoole
+ * lot of ugly boiler plate go away */
+#define LO_HANDLER(name) \
+int _##name##_cb (LO_HANDLER_ARGS);\
+inline static int name##_cb(LO_HANDLER_ARGS, void* myself)\
+{ return ((OSCEngineReceiver*)myself)->_##name##_cb(path, types, argv, argc, msg); }
+
+/* FIXME: Make this receive and preprocess in the same thread? */
+
+/** Receive OSC messages and call interface functions.
+ *
+ * \ingroup engine
+ */
+class OSCEngineReceiver
+{
+public:
+ OSCEngineReceiver(Engine& engine,
+ SharedPtr<ServerInterface> interface,
+ uint16_t port);
+
+ ~OSCEngineReceiver();
+
+private:
+ struct ReceiveThread : public Raul::Thread {
+ explicit ReceiveThread(OSCEngineReceiver& receiver) : _receiver(receiver) {}
+ virtual void _run();
+ private:
+ OSCEngineReceiver& _receiver;
+ };
+
+ friend struct ReceiveThread;
+
+ ReceiveThread* _receive_thread;
+
+ Raul::URI _delta_uri;
+ Resource::Properties _delta_remove;
+ Resource::Properties _delta_add;
+
+#ifdef LIBLO_BUNDLES
+ static int bundle_start_cb(lo_timetag time, void* myself) {
+ return ((OSCEngineReceiver*)myself)->_bundle_start_cb(time);
+ }
+ static int bundle_end_cb(void* myself) {
+ return ((OSCEngineReceiver*)myself)->_bundle_end_cb();
+ }
+
+ int _bundle_start_cb(lo_timetag time);
+ int _bundle_end_cb();
+#endif
+
+ static void error_cb(int num, const char* msg, const char* path);
+ static int set_response_address_cb(LO_HANDLER_ARGS, void* myself);
+ static int generic_cb(LO_HANDLER_ARGS, void* myself);
+ static int unknown_cb(LO_HANDLER_ARGS, void* myself);
+
+ LO_HANDLER(quit);
+ LO_HANDLER(ping);
+ LO_HANDLER(ping_slow);
+ LO_HANDLER(register_client);
+ LO_HANDLER(unregister_client);
+ LO_HANDLER(get);
+ LO_HANDLER(put);
+ LO_HANDLER(delta_begin);
+ LO_HANDLER(delta_remove);
+ LO_HANDLER(delta_add);
+ LO_HANDLER(delta_end);
+ LO_HANDLER(move);
+ LO_HANDLER(del);
+ LO_HANDLER(connect);
+ LO_HANDLER(disconnect);
+ LO_HANDLER(disconnect_all);
+ LO_HANDLER(note_on);
+ LO_HANDLER(note_off);
+ LO_HANDLER(all_notes_off);
+ LO_HANDLER(learn);
+ LO_HANDLER(set_property);
+ LO_HANDLER(property_set);
+
+ Engine& _engine;
+ SharedPtr<ServerInterface> _interface;
+ lo_server _server;
+};
+
+} // namespace Server
+} // namespace Ingen
+
+#endif // INGEN_ENGINE_OSCENGINERECEIVER_HPP
diff --git a/src/osc/ingen_osc.cpp b/src/osc/ingen_osc.cpp
new file mode 100644
index 00000000..4a8946c0
--- /dev/null
+++ b/src/osc/ingen_osc.cpp
@@ -0,0 +1,53 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen 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.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "ingen/shared/Module.hpp"
+#include "ingen/shared/World.hpp"
+
+#include "../server/Engine.hpp"
+#include "../server/ServerInterfaceImpl.hpp"
+
+#include "OSCEngineReceiver.hpp"
+
+using namespace std;
+using namespace Ingen;
+
+struct IngenOSCModule : public Ingen::Shared::Module {
+ void load(Ingen::Shared::World* world) {
+ Server::Engine* engine = (Server::Engine*)world->local_engine().get();
+ SharedPtr<Server::ServerInterfaceImpl> interface(
+ new Server::ServerInterfaceImpl(*engine));
+ receiver = SharedPtr<Server::OSCEngineReceiver>(
+ new Server::OSCEngineReceiver(
+ *engine,
+ interface,
+ world->conf()->option("engine-port").get_int32()));
+ engine->add_event_source(interface);
+ }
+
+ SharedPtr<Server::OSCEngineReceiver> receiver;
+};
+
+extern "C" {
+
+Ingen::Shared::Module*
+ingen_module_load()
+{
+ return new IngenOSCModule();
+}
+
+} // extern "C"