summaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/ClientStore.cpp648
-rw-r--r--src/client/ClientStore.hpp151
-rw-r--r--src/client/ConnectionModel.hpp76
-rw-r--r--src/client/DeprecatedLoader.cpp711
-rw-r--r--src/client/DeprecatedLoader.hpp94
-rw-r--r--src/client/HTTPClientReceiver.cpp97
-rw-r--r--src/client/HTTPClientReceiver.hpp62
-rw-r--r--src/client/HTTPEngineSender.cpp285
-rw-r--r--src/client/HTTPEngineSender.hpp155
-rw-r--r--src/client/Makefile.am69
-rw-r--r--src/client/NodeModel.cpp224
-rw-r--r--src/client/NodeModel.hpp101
-rw-r--r--src/client/OSCClientReceiver.cpp394
-rw-r--r--src/client/OSCClientReceiver.hpp107
-rw-r--r--src/client/OSCEngineSender.cpp420
-rw-r--r--src/client/OSCEngineSender.hpp154
-rw-r--r--src/client/ObjectModel.cpp148
-rw-r--r--src/client/ObjectModel.hpp115
-rw-r--r--src/client/PatchModel.cpp190
-rw-r--r--src/client/PatchModel.hpp105
-rw-r--r--src/client/PluginModel.cpp143
-rw-r--r--src/client/PluginModel.hpp142
-rw-r--r--src/client/PluginUI.cpp157
-rw-r--r--src/client/PluginUI.hpp65
-rw-r--r--src/client/PortModel.cpp66
-rw-r--r--src/client/PortModel.hpp112
-rw-r--r--src/client/SigClientInterface.hpp156
-rw-r--r--src/client/ThreadedSigClientInterface.cpp78
-rw-r--r--src/client/ThreadedSigClientInterface.hpp184
-rw-r--r--src/client/client.cpp57
-rw-r--r--src/client/client.hpp43
-rw-r--r--src/client/wscript35
32 files changed, 5544 insertions, 0 deletions
diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp
new file mode 100644
index 00000000..18582046
--- /dev/null
+++ b/src/client/ClientStore.cpp
@@ -0,0 +1,648 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <raul/PathTable.hpp>
+#include "ClientStore.hpp"
+#include "ObjectModel.hpp"
+#include "PatchModel.hpp"
+#include "NodeModel.hpp"
+#include "PortModel.hpp"
+#include "PluginModel.hpp"
+#include "PatchModel.hpp"
+#include "SigClientInterface.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Client {
+
+
+ClientStore::ClientStore(SharedPtr<EngineInterface> engine, SharedPtr<SigClientInterface> emitter)
+ : _engine(engine)
+ , _emitter(emitter)
+ , _plugins(new Plugins())
+{
+ _handle_orphans = (engine && emitter);
+
+ if (!emitter)
+ return;
+
+ emitter->signal_object_destroyed.connect(sigc::mem_fun(this, &ClientStore::destroy));
+ emitter->signal_object_renamed.connect(sigc::mem_fun(this, &ClientStore::rename));
+ emitter->signal_new_plugin.connect(sigc::mem_fun(this, &ClientStore::new_plugin));
+ emitter->signal_new_patch.connect(sigc::mem_fun(this, &ClientStore::new_patch));
+ emitter->signal_new_node.connect(sigc::mem_fun(this, &ClientStore::new_node));
+ emitter->signal_new_port.connect(sigc::mem_fun(this, &ClientStore::new_port));
+ emitter->signal_patch_cleared.connect(sigc::mem_fun(this, &ClientStore::patch_cleared));
+ emitter->signal_connection.connect(sigc::mem_fun(this, &ClientStore::connect));
+ emitter->signal_disconnection.connect(sigc::mem_fun(this, &ClientStore::disconnect));
+ emitter->signal_variable_change.connect(sigc::mem_fun(this, &ClientStore::set_variable));
+ emitter->signal_property_change.connect(sigc::mem_fun(this, &ClientStore::set_property));
+ emitter->signal_port_value.connect(sigc::mem_fun(this, &ClientStore::set_port_value));
+ emitter->signal_voice_value.connect(sigc::mem_fun(this, &ClientStore::set_voice_value));
+ emitter->signal_port_activity.connect(sigc::mem_fun(this, &ClientStore::port_activity));
+}
+
+
+void
+ClientStore::clear()
+{
+ Store::clear();
+ _plugins->clear();
+}
+
+
+void
+ClientStore::add_plugin_orphan(SharedPtr<NodeModel> node)
+{
+ if (!_handle_orphans)
+ return;
+
+ Raul::Table<string, list<SharedPtr<NodeModel> > >::iterator spawn
+ = _plugin_orphans.find(node->plugin_uri());
+
+ if (spawn != _plugin_orphans.end()) {
+ spawn->second.push_back(node);
+ } else {
+ cerr << "WARNING: Orphans of plugin " << node->plugin_uri() << " received" << endl;
+ _engine->request_plugin(node->plugin_uri());
+ list<SharedPtr<NodeModel> > l;
+ l.push_back(node);
+ _plugin_orphans[node->plugin_uri()] = l;
+ }
+}
+
+
+void
+ClientStore::resolve_plugin_orphans(SharedPtr<PluginModel> plugin)
+{
+ if (!_handle_orphans)
+ return;
+ Raul::Table<string, list<SharedPtr<NodeModel> > >::iterator n
+ = _plugin_orphans.find(plugin->uri());
+
+ if (n != _plugin_orphans.end()) {
+
+ list<SharedPtr<NodeModel> > spawn = n->second; // take a copy
+ cerr << "Missing dependant " << plugin->uri() << " received" << endl;
+
+ _plugin_orphans.erase(plugin->uri()); // prevent infinite recursion
+
+ for (list<SharedPtr<NodeModel> >::iterator i = spawn.begin();
+ i != spawn.end(); ++i) {
+ (*i)->_plugin = plugin;
+ //add_object(*i);
+ }
+ }
+}
+
+
+void
+ClientStore::add_connection_orphan(std::pair<Path, Path> orphan)
+{
+ // Do this anyway, it's needed to get the connections for copy&paste
+ //if (!_handle_orphans)
+ //return;
+
+ if (_handle_orphans)
+ cerr << "WARNING: Orphan connection " << orphan.first
+ << " -> " << orphan.second << " received." << endl;
+
+ _connection_orphans.push_back(orphan);
+}
+
+
+void
+ClientStore::resolve_connection_orphans(SharedPtr<PortModel> port)
+{
+ if (!_handle_orphans)
+ return;
+ assert(port->parent());
+
+ for (list< pair<Path, Path> >::iterator c = _connection_orphans.begin();
+ c != _connection_orphans.end(); ) {
+
+ list< pair<Path, Path> >::iterator next = c;
+ ++next;
+
+ if (c->first == port->path() || c->second == port->path()) {
+ cerr << "Missing dependant (" << c->first << " -> " << c->second << ") received" << endl;
+ bool success = attempt_connection(c->first, c->second);
+ if (success)
+ _connection_orphans.erase(c);
+ }
+
+ c = next;
+ }
+}
+
+
+void
+ClientStore::add_orphan(SharedPtr<ObjectModel> child)
+{
+ if (!_handle_orphans)
+ return;
+ cerr << "WARNING: Orphan object " << child->path() << " received." << endl;
+
+ Raul::PathTable<list<SharedPtr<ObjectModel> > >::iterator children
+ = _orphans.find(child->path().parent());
+
+ _engine->request_object(child->path().parent());
+
+ if (children != _orphans.end()) {
+ children->second.push_back(child);
+ } else {
+ list<SharedPtr<ObjectModel> > l;
+ l.push_back(child);
+ _orphans.insert(make_pair(child->path().parent(), l));
+ }
+}
+
+
+void
+ClientStore::add_variable_orphan(const Path& subject_path, const string& predicate, const Atom& value)
+{
+ if (!_handle_orphans)
+ return;
+ Raul::PathTable<list<std::pair<string, Atom> > >::iterator orphans
+ = _variable_orphans.find(subject_path);
+
+ _engine->request_object(subject_path);
+
+ if (orphans != _variable_orphans.end()) {
+ orphans->second.push_back(std::pair<string, Atom>(predicate, value));
+ } else {
+ list<std::pair<string, Atom> > l;
+ l.push_back(std::pair<string, Atom>(predicate, value));
+ _variable_orphans[subject_path] = l;
+ }
+}
+
+
+void
+ClientStore::resolve_variable_orphans(SharedPtr<ObjectModel> subject)
+{
+ if (!_handle_orphans)
+ return;
+ Raul::PathTable<list<std::pair<string, Atom> > >::iterator v
+ = _variable_orphans.find(subject->path());
+
+ if (v != _variable_orphans.end()) {
+
+ list<std::pair<string, Atom> > values = v->second; // take a copy
+
+ _variable_orphans.erase(subject->path());
+ cerr << "Missing dependant " << subject->path() << " received" << endl;
+
+ for (list<std::pair<string, Atom> >::iterator i = values.begin();
+ i != values.end(); ++i) {
+ subject->set_variable(i->first, i->second);
+ }
+ }
+}
+
+
+void
+ClientStore::resolve_orphans(SharedPtr<ObjectModel> parent)
+{
+ if (!_handle_orphans)
+ return;
+ Raul::PathTable<list<SharedPtr<ObjectModel> > >::iterator c
+ = _orphans.find(parent->path());
+
+ if (c != _orphans.end()) {
+
+ list<SharedPtr<ObjectModel> > children = c->second; // take a copy
+
+ _orphans.erase(parent->path()); // prevent infinite recursion
+
+ for (list<SharedPtr<ObjectModel> >::iterator i = children.begin();
+ i != children.end(); ++i) {
+ add_object(*i);
+ }
+ }
+}
+
+
+void
+ClientStore::add_object(SharedPtr<ObjectModel> object)
+{
+ // If we already have "this" object, merge the existing one into the new
+ // one (with precedence to the new values).
+ iterator existing = find(object->path());
+ if (existing != end()) {
+ PtrCast<ObjectModel>(existing->second)->set(object);
+ } else {
+
+ if (object->path() != "/") {
+ SharedPtr<ObjectModel> parent = this->object(object->path().parent());
+ if (parent) {
+ assert(object->path().is_child_of(parent->path()));
+ object->set_parent(parent);
+ parent->add_child(object);
+ assert(parent && (object->parent() == parent));
+
+ (*this)[object->path()] = object;
+ signal_new_object.emit(object);
+
+ resolve_variable_orphans(parent);
+ resolve_orphans(parent);
+
+ SharedPtr<PortModel> port = PtrCast<PortModel>(object);
+ if (port)
+ resolve_connection_orphans(port);
+
+ } else {
+ add_orphan(object);
+ }
+ } else {
+ (*this)[object->path()] = object;
+ signal_new_object.emit(object);
+ }
+
+ }
+
+ /*cout << "[Store] Added " << object->path() << " {" << endl;
+ for (iterator i = begin(); i != end(); ++i) {
+ cout << "\t" << i->first << endl;
+ }
+ cout << "}" << endl;*/
+}
+
+
+SharedPtr<ObjectModel>
+ClientStore::remove_object(const Path& path)
+{
+ iterator i = find(path);
+
+ if (i != end()) {
+ assert((*i).second->path() == path);
+ SharedPtr<ObjectModel> result = PtrCast<ObjectModel>((*i).second);
+ assert(result);
+ //erase(i);
+ iterator descendants_end = find_descendants_end(i);
+ SharedPtr<Store::Objects> removed = yank(i, descendants_end);
+
+ /*cout << "[Store] Removing " << i->first << " {" << endl;
+ for (iterator i = removed.begin(); i != removed.end(); ++i) {
+ cout << "\t" << i->first << endl;
+ }
+ cout << "}" << endl;*/
+
+ if (result)
+ result->signal_destroyed.emit();
+
+ if (result->path() != "/") {
+ assert(result->parent());
+
+ SharedPtr<ObjectModel> parent = this->object(result->path().parent());
+ if (parent) {
+ parent->remove_child(result);
+ }
+ }
+
+ assert(!object(path));
+
+ return result;
+
+ } else {
+ return SharedPtr<ObjectModel>();
+ }
+}
+
+
+SharedPtr<PluginModel>
+ClientStore::plugin(const string& uri)
+{
+ assert(uri.length() > 0);
+ Plugins::iterator i = _plugins->find(uri);
+ if (i == _plugins->end())
+ return SharedPtr<PluginModel>();
+ else
+ return (*i).second;
+}
+
+
+SharedPtr<ObjectModel>
+ClientStore::object(const Path& path)
+{
+ assert(path.length() > 0);
+ iterator i = find(path);
+ if (i == end()) {
+ return SharedPtr<ObjectModel>();
+ } else {
+ SharedPtr<ObjectModel> model = PtrCast<ObjectModel>(i->second);
+ assert(model);
+ assert(model->path() == "/" || model->parent());
+ return model;
+ }
+}
+
+void
+ClientStore::add_plugin(SharedPtr<PluginModel> pm)
+{
+ // FIXME: dupes? merge, like with objects?
+
+ (*_plugins)[pm->uri()] = pm;
+ signal_new_plugin(pm);
+ //cerr << "Plugin: " << pm->uri() << ", # plugins: " << _plugins->size() << endl;
+}
+
+
+/* ****** Signal Handlers ******** */
+
+
+void
+ClientStore::destroy(const std::string& path)
+{
+ SharedPtr<ObjectModel> removed = remove_object(path);
+ removed.reset();
+ //cerr << "[ClientStore] removed object " << path << ", count: " << removed.use_count();
+}
+
+void
+ClientStore::rename(const Path& old_path, const Path& new_path)
+{
+ iterator parent = find(old_path);
+ if (parent == end()) {
+ cerr << "[Store] Failed to find object " << old_path << " to rename." << endl;
+ return;
+ }
+
+ iterator descendants_end = find_descendants_end(parent);
+
+ SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > removed
+ = yank(parent, descendants_end);
+
+ assert(removed->size() > 0);
+
+ for (Table<Path, SharedPtr<Shared::GraphObject> >::iterator i = removed->begin(); i != removed->end(); ++i) {
+ const Path& child_old_path = i->first;
+ assert(Path::descendant_comparator(old_path, child_old_path));
+
+ Path child_new_path;
+ if (child_old_path == old_path)
+ child_new_path = new_path;
+ else
+ child_new_path = new_path.base() + child_old_path.substr(old_path.length()+1);
+
+ cerr << "[Store] Renamed " << child_old_path << " -> " << child_new_path << endl;
+ PtrCast<ObjectModel>(i->second)->set_path(child_new_path);
+ i->first = child_new_path;
+ }
+
+ cram(*removed.get());
+
+ //cerr << "[Store] Table:" << endl;
+ //for (size_t i=0; i < removed.size(); ++i) {
+ // cerr << removed[i].first << "\t\t: " << removed[i].second << endl;
+ //}
+ /*for (iterator i = begin(); i != end(); ++i) {
+ cerr << i->first << "\t\t: " << i->second << endl;
+ }*/
+}
+
+void
+ClientStore::new_plugin(const string& uri, const string& type_uri, const string& symbol, const string& name)
+{
+ SharedPtr<PluginModel> p(new PluginModel(uri, type_uri, symbol, name));
+ add_plugin(p);
+ resolve_plugin_orphans(p);
+}
+
+
+void
+ClientStore::new_patch(const string& path, uint32_t poly)
+{
+ SharedPtr<PatchModel> p(new PatchModel(path, poly));
+ add_object(p);
+}
+
+
+void
+ClientStore::new_node(const string& path, const string& plugin_uri)
+{
+ SharedPtr<PluginModel> plug = plugin(plugin_uri);
+ if (!plug) {
+ SharedPtr<NodeModel> n(new NodeModel(plugin_uri, path));
+ add_plugin_orphan(n);
+ add_object(n);
+ } else {
+ SharedPtr<NodeModel> n(new NodeModel(plug, path));
+ add_object(n);
+ }
+}
+
+
+void
+ClientStore::new_port(const string& path, uint32_t index, const string& type, bool is_output)
+{
+ PortModel::Direction pdir = is_output ? PortModel::OUTPUT : PortModel::INPUT;
+
+ SharedPtr<PortModel> p(new PortModel(path, index, type, pdir));
+ add_object(p);
+ if (p->parent())
+ resolve_connection_orphans(p);
+}
+
+
+void
+ClientStore::patch_cleared(const Path& path)
+{
+ iterator i = find(path);
+ if (i != end()) {
+ assert((*i).second->path() == path);
+ SharedPtr<PatchModel> patch = PtrCast<PatchModel>(i->second);
+
+ iterator first_descendant = i;
+ ++first_descendant;
+ iterator descendants_end = find_descendants_end(i);
+ SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > removed
+ = yank(first_descendant, descendants_end);
+
+ for (iterator i = removed->begin(); i != removed->end(); ++i) {
+ SharedPtr<ObjectModel> model = PtrCast<ObjectModel>(i->second);
+ assert(model);
+ model->signal_destroyed.emit();
+ if (model->parent() == patch)
+ patch->remove_child(model);
+ }
+
+ } else {
+ cerr << "[Store] Unable to find patch " << path << " to clear." << endl;
+ }
+}
+
+
+void
+ClientStore::set_variable(const string& subject_path, const string& predicate, const Atom& value)
+{
+ SharedPtr<ObjectModel> subject = object(subject_path);
+
+ if (!value.is_valid()) {
+ cerr << "ERROR: variable '" << predicate << "' has no type" << endl;
+ } else if (subject) {
+ subject->set_variable(predicate, value);
+ } else {
+ add_variable_orphan(subject_path, predicate, value);
+ cerr << "WARNING: variable for unknown object " << subject_path << endl;
+ }
+}
+
+
+void
+ClientStore::set_property(const string& subject_path, const string& predicate, const Atom& value)
+{
+ SharedPtr<ObjectModel> subject = object(subject_path);
+
+ if (!value.is_valid()) {
+ cerr << "ERROR: property '" << predicate << "' has no type" << endl;
+ } else if (subject) {
+ subject->set_property(predicate, value);
+ } else {
+ cerr << "WARNING: property for unknown object " << subject_path
+ << " lost. Client must refresh!" << endl;
+ }
+}
+
+
+void
+ClientStore::set_port_value(const string& port_path, const Raul::Atom& value)
+{
+ SharedPtr<PortModel> port = PtrCast<PortModel>(object(port_path));
+ if (port)
+ port->value(value);
+ else
+ cerr << "ERROR: control change for nonexistant port " << port_path << endl;
+}
+
+
+void
+ClientStore::set_voice_value(const string& port_path, uint32_t voice, const Raul::Atom& value)
+{
+ SharedPtr<PortModel> port = PtrCast<PortModel>(object(port_path));
+ if (port)
+ port->value(voice, value);
+ else
+ cerr << "ERROR: poly control change for nonexistant port " << port_path << endl;
+}
+
+
+void
+ClientStore::port_activity(const Path& port_path)
+{
+ SharedPtr<PortModel> port = PtrCast<PortModel>(object(port_path));
+ if (port)
+ port->signal_activity.emit();
+ else
+ cerr << "ERROR: activity for nonexistant port " << port_path << endl;
+}
+
+
+SharedPtr<PatchModel>
+ClientStore::connection_patch(const Path& src_port_path, const Path& dst_port_path)
+{
+ SharedPtr<PatchModel> patch;
+
+ if (src_port_path.parent() == dst_port_path.parent())
+ patch = PtrCast<PatchModel>(this->object(src_port_path.parent()));
+
+ if (!patch && src_port_path.parent() == dst_port_path.parent().parent())
+ patch = PtrCast<PatchModel>(this->object(src_port_path.parent()));
+
+ if (!patch && src_port_path.parent().parent() == dst_port_path.parent())
+ patch = PtrCast<PatchModel>(this->object(dst_port_path.parent()));
+
+ if (!patch)
+ patch = PtrCast<PatchModel>(this->object(src_port_path.parent().parent()));
+
+ if (!patch)
+ cerr << "ERROR: Unable to find connection patch " << src_port_path
+ << " -> " << dst_port_path << endl;
+
+ return patch;
+}
+
+
+bool
+ClientStore::attempt_connection(const Path& src_port_path, const Path& dst_port_path, bool add_orphan)
+{
+ SharedPtr<PortModel> src_port = PtrCast<PortModel>(object(src_port_path));
+ SharedPtr<PortModel> dst_port = PtrCast<PortModel>(object(dst_port_path));
+
+ if (src_port && dst_port) {
+ assert(src_port->parent());
+ assert(dst_port->parent());
+
+ SharedPtr<PatchModel> patch = connection_patch(src_port_path, dst_port_path);
+ assert(patch);
+
+ SharedPtr<ConnectionModel> cm(new ConnectionModel(src_port, dst_port));
+
+ src_port->connected_to(dst_port);
+ dst_port->connected_to(src_port);
+
+ patch->add_connection(cm);
+ return true;
+ } else if (add_orphan) {
+ add_connection_orphan(make_pair(src_port_path, dst_port_path));
+ }
+
+ return false;
+}
+
+
+void
+ClientStore::connect(const string& src_port_path, const string& dst_port_path)
+{
+ attempt_connection(src_port_path, dst_port_path, true);
+}
+
+
+void
+ClientStore::disconnect(const string& src_port_path, const string& dst_port_path)
+{
+ // Find the ports and create a ConnectionModel just to get at the parent path
+ // finding logic in ConnectionModel. So I'm lazy.
+
+ SharedPtr<PortModel> src_port = PtrCast<PortModel>(object(src_port_path));
+ SharedPtr<PortModel> dst_port = PtrCast<PortModel>(object(dst_port_path));
+
+ if (src_port)
+ src_port->disconnected_from(dst_port);
+ else
+ cerr << "WARNING: Disconnection from nonexistant src port " << src_port_path << endl;
+
+ if (dst_port)
+ dst_port->disconnected_from(dst_port);
+ else
+ cerr << "WARNING: Disconnection from nonexistant dst port " << dst_port_path << endl;
+
+ SharedPtr<PatchModel> patch = connection_patch(src_port_path, dst_port_path);
+
+ if (patch)
+ patch->remove_connection(src_port_path, dst_port_path);
+ else
+ cerr << "ERROR: disconnection in nonexistant patch: "
+ << src_port_path << " -> " << dst_port_path << endl;
+}
+
+
+} // namespace Client
+} // namespace Ingen
+
diff --git a/src/client/ClientStore.hpp b/src/client/ClientStore.hpp
new file mode 100644
index 00000000..f08fcd9b
--- /dev/null
+++ b/src/client/ClientStore.hpp
@@ -0,0 +1,151 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 CLIENT_STORE_HPP
+#define CLIENT_STORE_HPP
+
+#include <cassert>
+#include <string>
+#include <list>
+#include <raul/SharedPtr.hpp>
+#include <sigc++/sigc++.h>
+#include <raul/Path.hpp>
+#include <raul/Atom.hpp>
+#include <raul/PathTable.hpp>
+#include <raul/TableImpl.hpp>
+#include "interface/EngineInterface.hpp"
+#include "shared/Store.hpp"
+using std::string; using std::list;
+using Ingen::Shared::EngineInterface;
+using Raul::Path;
+using Raul::Atom;
+
+namespace Ingen {
+
+namespace Shared { class GraphObject; }
+
+namespace Client {
+
+class SigClientInterface;
+class ObjectModel;
+class PluginModel;
+class PatchModel;
+class NodeModel;
+class PortModel;
+class ConnectionModel;
+
+
+/** Automatically manages models of objects in the engine.
+ *
+ * \ingroup IngenClient
+ */
+class ClientStore : public Shared::Store, public Shared::CommonInterface, public sigc::trackable {
+public:
+ ClientStore(SharedPtr<EngineInterface> engine=SharedPtr<EngineInterface>(),
+ SharedPtr<SigClientInterface> emitter=SharedPtr<SigClientInterface>());
+
+ SharedPtr<PluginModel> plugin(const string& uri);
+ SharedPtr<ObjectModel> object(const Path& path);
+
+ void clear();
+
+ typedef Raul::Table<string, SharedPtr<PluginModel> > Plugins;
+ SharedPtr<const Plugins> plugins() const { return _plugins; }
+ SharedPtr<Plugins> plugins() { return _plugins; }
+ void set_plugins(SharedPtr<Plugins> p) { _plugins = p; }
+
+ // CommonInterface
+ void new_plugin(const string& uri, const string& type_uri, const string& symbol, const string& name);
+ void new_patch(const string& path, uint32_t poly);
+ void new_node(const string& path, const string& plugin_uri);
+ void new_port(const string& path, uint32_t index, const string& data_type, bool is_output);
+ void set_variable(const string& subject_path, const string& predicate, const Atom& value);
+ void set_property(const string& subject_path, const string& predicate, const Atom& value);
+ void set_port_value(const string& port_path, const Raul::Atom& value);
+ void set_voice_value(const string& port_path, uint32_t voice, const Raul::Atom& value);
+ void connect(const string& src_port_path, const string& dst_port_path);
+ void disconnect(const string& src_port_path, const string& dst_port_path);
+ void destroy(const string& path);
+
+ typedef list< std::pair<Path, Path> > ConnectionRecords;
+ const ConnectionRecords& connection_records() { return _connection_orphans; }
+
+ sigc::signal<void, SharedPtr<ObjectModel> > signal_new_object;
+ sigc::signal<void, SharedPtr<PluginModel> > signal_new_plugin;
+
+private:
+
+ void add(Shared::GraphObject* o) { throw; }
+
+ void add_object(SharedPtr<ObjectModel> object);
+ SharedPtr<ObjectModel> remove_object(const Path& path);
+
+ void add_plugin(SharedPtr<PluginModel> plugin);
+
+ SharedPtr<PatchModel> connection_patch(const Path& src_port_path, const Path& dst_port_path);
+
+ // It would be nice to integrate these somehow..
+
+ void add_orphan(SharedPtr<ObjectModel> orphan);
+ void resolve_orphans(SharedPtr<ObjectModel> parent);
+
+ void add_connection_orphan(std::pair<Path, Path> orphan);
+ void resolve_connection_orphans(SharedPtr<PortModel> port);
+
+ void add_plugin_orphan(SharedPtr<NodeModel> orphan);
+ void resolve_plugin_orphans(SharedPtr<PluginModel> plugin);
+
+ void add_variable_orphan(const Path& subject, const string& predicate, const Atom& value);
+ void resolve_variable_orphans(SharedPtr<ObjectModel> subject);
+
+ void bundle_begin() {}
+ void bundle_end() {}
+
+ // Slots for SigClientInterface signals
+ void rename(const Path& old_path, const Path& new_path);
+ void patch_cleared(const Path& path);
+ void port_activity(const Path& port_path);
+
+ bool attempt_connection(const Path& src_port_path, const Path& dst_port_path, bool add_orphan=false);
+
+ bool _handle_orphans;
+
+ SharedPtr<EngineInterface> _engine;
+ SharedPtr<SigClientInterface> _emitter;
+
+ SharedPtr<Plugins> _plugins; ///< Map, keyed by plugin URI
+
+ /** Objects we've received, but depend on the existance of another unknown object.
+ * Keyed by the path of the depended-on object (for tolerance of orderless comms) */
+ Raul::PathTable<list<SharedPtr<ObjectModel> > > _orphans;
+
+ /** Same idea, except with plugins instead of parents.
+ * It's unfortunate everything doesn't just have a URI and this was the same.. ahem.. */
+ Raul::Table<string, list<SharedPtr<NodeModel> > > _plugin_orphans;
+
+ /** Not orphans OF variable like the above, but orphans which are variable */
+ Raul::PathTable<list<std::pair<string, Atom> > > _variable_orphans;
+
+ /** Ditto */
+ ConnectionRecords _connection_orphans;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // CLIENT_STORE_HPP
diff --git a/src/client/ConnectionModel.hpp b/src/client/ConnectionModel.hpp
new file mode 100644
index 00000000..91c448df
--- /dev/null
+++ b/src/client/ConnectionModel.hpp
@@ -0,0 +1,76 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 CONNECTIONMODEL_H
+#define CONNECTIONMODEL_H
+
+#include <cassert>
+#include <string>
+#include <list>
+#include <raul/Path.hpp>
+#include <raul/SharedPtr.hpp>
+#include "interface/Connection.hpp"
+#include "PortModel.hpp"
+
+namespace Ingen {
+namespace Client {
+
+class ClientStore;
+
+
+/** Class to represent a port->port connection in the engine.
+ *
+ * This can either have pointers to the connection ports' models, or just
+ * paths as strings. The engine passes just strings (by necessity), but
+ * clients can set the pointers then they don't have to worry about port
+ * renaming, as the connections will always return the port's path, even
+ * if it changes.
+ *
+ * \ingroup IngenClient
+ */
+class ConnectionModel : public Shared::Connection
+{
+public:
+ SharedPtr<PortModel> src_port() const { return _src_port; }
+ SharedPtr<PortModel> dst_port() const { return _dst_port; }
+
+ const Path src_port_path() const { return _src_port->path(); }
+ const Path dst_port_path() const { return _dst_port->path(); }
+
+private:
+ friend class ClientStore;
+
+ ConnectionModel(SharedPtr<PortModel> src, SharedPtr<PortModel> dst)
+ : _src_port(src)
+ , _dst_port(dst)
+ {
+ assert(_src_port);
+ assert(_dst_port);
+ assert(_src_port->parent());
+ assert(_dst_port->parent());
+ assert(_src_port->path() != _dst_port->path());
+ }
+
+ const SharedPtr<PortModel> _src_port;
+ const SharedPtr<PortModel> _dst_port;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // CONNECTIONMODEL_H
diff --git a/src/client/DeprecatedLoader.cpp b/src/client/DeprecatedLoader.cpp
new file mode 100644
index 00000000..a07893f7
--- /dev/null
+++ b/src/client/DeprecatedLoader.cpp
@@ -0,0 +1,711 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <iostream>
+#include <fstream>
+#include <vector>
+#include <algorithm>
+#include <utility> // for pair, make_pair
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <cstdlib> // for atof
+#include <cmath>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <raul/Path.hpp>
+#include "interface/EngineInterface.hpp"
+#include "PatchModel.hpp"
+#include "NodeModel.hpp"
+#include "ConnectionModel.hpp"
+#include "PortModel.hpp"
+#include "PluginModel.hpp"
+#include "DeprecatedLoader.hpp"
+
+#define NS_INGEN "http://drobilla.net/ns/ingen#"
+
+using namespace std;
+
+namespace Ingen {
+namespace Client {
+
+
+/** A single port's control setting (in a preset).
+ *
+ * \ingroup IngenClient
+ */
+class ControlModel
+{
+public:
+ ControlModel(const Path& port_path, float value)
+ : _port_path(port_path)
+ , _value(value)
+ {
+ assert(_port_path.find("//") == string::npos);
+ }
+
+ const Path& port_path() const { return _port_path; }
+ void port_path(const string& p) { _port_path = p; }
+ float value() const { return _value; }
+ void value(float v) { _value = v; }
+
+private:
+ Path _port_path;
+ float _value;
+};
+
+
+/** Model of a preset (a collection of control settings).
+ *
+ * \ingroup IngenClient
+ */
+class PresetModel
+{
+public:
+ PresetModel(const string& base_path) : _base_path(base_path) {}
+
+ /** Add a control value to this preset. An empty string for a node_name
+ * means the port is on the patch itself (not a node in the patch). */
+ void add_control(const string& node_name, string port_name, float value) {
+ if (port_name == "note_number") // FIXME: filthy kludge
+ port_name = "note";
+
+ if (node_name != "")
+ _controls.push_back(ControlModel(_base_path + node_name +"/"+ port_name, value));
+ else
+ _controls.push_back(ControlModel(_base_path + port_name, value));
+ }
+
+ const string& name() const { return _name; }
+ void name(const string& n) { _name = n; }
+
+ const list<ControlModel>& controls() const { return _controls; }
+
+private:
+ string _name;
+ string _base_path;
+ list<ControlModel> _controls;
+};
+
+
+string
+DeprecatedLoader::nameify_if_invalid(const string& name)
+{
+ if (Path::is_valid_name(name)) {
+ return name;
+ } else {
+ const string new_name = Path::nameify(name);
+ assert(Path::is_valid_name(new_name));
+ if (new_name != name)
+ cerr << "WARNING: Illegal name '" << name << "' converted to '"
+ << new_name << "'" << endl;
+ return new_name;
+ }
+}
+
+
+string
+DeprecatedLoader::translate_load_path(const string& path)
+{
+ std::map<string,string>::iterator t = _load_path_translations.find(path);
+
+ if (t != _load_path_translations.end()) {
+ assert(Path::is_valid((*t).second));
+ return (*t).second;
+ // Filthy, filthy kludges
+ // (FIXME: apply these less heavy handedly, only when it's an internal module)
+ } else if (path.find("midi") != string::npos) {
+ assert(Path::is_valid(path));
+ if (path.substr(path.find_last_of("/")) == "/MIDI_In")
+ return path.substr(0, path.find_last_of("/")) + "/input";
+ else if (path.substr(path.find_last_of("/")) == "/Note_Number")
+ return path.substr(0, path.find_last_of("/")) + "/note";
+ else if (path.substr(path.find_last_of("/")) == "/Gate")
+ return path.substr(0, path.find_last_of("/")) + "/gate";
+ else if (path.substr(path.find_last_of("/")) == "/Trigger")
+ return path.substr(0, path.find_last_of("/")) + "/trigger";
+ else if (path.substr(path.find_last_of("/")) == "/Velocity")
+ return path.substr(0, path.find_last_of("/")) + "/velocity";
+ else
+ return path;
+ } else {
+ return path;
+ }
+}
+
+
+/** Add a piece of data to a Variables, translating from deprecated unqualified keys
+ *
+ * Adds a namespace prefix for known keys, and ignores the rest.
+ */
+void
+DeprecatedLoader::add_variable(GraphObject::Variables& data, string old_key, string value)
+{
+ string key = "";
+ if (old_key == "module-x")
+ key = "ingenuity:canvas-x";
+ else if (old_key == "module-y")
+ key = "ingenuity:canvas-y";
+
+ if (key != "") {
+ // FIXME: should this overwrite existing values?
+ if (data.find(key) == data.end()) {
+ // Hack to make module-x and module-y set as floats
+ char* c_val = strdup(value.c_str());
+ char* endptr = NULL;
+
+ // FIXME: locale kludges
+ char* locale = strdup(setlocale(LC_NUMERIC, NULL));
+
+ float fval = strtof(c_val, &endptr);
+
+ setlocale(LC_NUMERIC, locale);
+ free(locale);
+
+ if (endptr != c_val && *endptr == '\0')
+ data[key] = Atom(fval);
+ else
+ data[key] = Atom(value);
+
+ free(c_val);
+ }
+ }
+}
+
+
+/** Load a patch in to the engine (and client) from a patch file.
+ *
+ * The name and poly from the passed PatchModel are used. If the name is
+ * the empty string, the name will be loaded from the file. If the poly
+ * is 0, it will be loaded from file. Otherwise the given values will
+ * be used.
+ *
+ * @param filename Local name of file to load patch from
+ *
+ * @param parent_path Patch to load this patch as a child of (empty string to load
+ * to the root patch)
+ *
+ * @param name Name of this patch (loaded/generated if the empty string)
+ * @param poly Polyphony of this patch (loaded/generated if 0)
+ *
+ * @param initial_data will be set last, so values passed there will override
+ * any values loaded from the patch file.
+ *
+ * @param existing If true, the patch will be loaded into a currently
+ * existing patch (ie a merging will take place). Errors will result
+ * if Nodes of conflicting names exist.
+ *
+ * Returns the path of the newly created patch.
+ */
+string
+DeprecatedLoader::load_patch(const Glib::ustring& filename,
+ boost::optional<Path> parent_path,
+ string name,
+ GraphObject::Variables initial_data,
+ bool existing)
+{
+ cerr << "[DeprecatedLoader] Loading patch " << filename << " under "
+ << parent_path << " / " << name << endl;
+
+ Path path = parent_path ? (parent_path.get().base() + name)
+ : "/" + name;
+
+ const bool load_name = (name == "");
+
+ size_t poly = 0;
+
+ /* Use parameter overridden polyphony, if given */
+ GraphObject::Variables::iterator poly_param = initial_data.find("ingen:polyphony");
+ if (poly_param != initial_data.end() && poly_param->second.type() == Atom::INT)
+ poly = poly_param->second.get_int32();
+
+ if (initial_data.find("filename") == initial_data.end())
+ initial_data["filename"] = Atom(filename.c_str()); // FIXME: URL?
+
+ xmlDocPtr doc = xmlParseFile(filename.c_str());
+
+ if (!doc) {
+ cerr << "Unable to parse patch file." << endl;
+ return "";
+ }
+
+ xmlNodePtr cur = xmlDocGetRootElement(doc);
+
+ if (!cur) {
+ cerr << "Empty document." << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) {
+ cerr << "File is not an Ingen patch file (root node != <patch>)" << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ xmlChar* key = NULL;
+ cur = cur->xmlChildrenNode;
+
+ // Load Patch attributes
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ if (load_name && key) {
+ if (parent_path)
+ path = Path(parent_path.get()).base() + nameify_if_invalid((char*)key);
+ else
+ path = Path("/");
+ }
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ if (poly == 0) {
+ poly = atoi((char*)key);
+ }
+ } else if (xmlStrcmp(cur->name, (const xmlChar*)"connection")
+ && xmlStrcmp(cur->name, (const xmlChar*)"node")
+ && xmlStrcmp(cur->name, (const xmlChar*)"subpatch")
+ && xmlStrcmp(cur->name, (const xmlChar*)"filename")
+ && xmlStrcmp(cur->name, (const xmlChar*)"preset")) {
+ // Don't know what this tag is, add it as variable without overwriting
+ // (so caller can set arbitrary parameters which will be preserved)
+ if (key)
+ add_variable(initial_data, (const char*)cur->name, (const char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (poly == 0)
+ poly = 1;
+
+ cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!! LOADING " << path << endl;
+
+ // Create it, if we're not merging
+ if (!existing && path != "/") {
+ _engine->new_patch(path, poly);
+ for (GraphObject::Variables::const_iterator i = initial_data.begin(); i != initial_data.end(); ++i)
+ _engine->set_variable(path, i->first, i->second);
+ }
+
+ // Load nodes
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"node")))
+ load_node(path, doc, cur);
+
+ cur = cur->next;
+ }
+
+ // Load subpatches
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) {
+ load_subpatch(filename.substr(0, filename.find_last_of("/")), path, doc, cur);
+ }
+ cur = cur->next;
+ }
+
+ // Load connections
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) {
+ load_connection(path, doc, cur);
+ }
+ cur = cur->next;
+ }
+
+ // Load presets (control values)
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ // I don't think Om ever wrote any preset other than "default"...
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) {
+ SharedPtr<PresetModel> pm = load_preset(path, doc, cur);
+ assert(pm != NULL);
+ if (pm->name() == "default") {
+ list<ControlModel>::const_iterator i = pm->controls().begin();
+ for ( ; i != pm->controls().end(); ++i) {
+ const float value = i->value();
+ _engine->set_port_value(translate_load_path(i->port_path()), Atom(value));
+ }
+ } else {
+ cerr << "WARNING: Unknown preset: \"" << pm->name() << endl;
+ }
+ }
+ cur = cur->next;
+ }
+
+ xmlFreeDoc(doc);
+ xmlCleanupParser();
+
+ // Done above.. late enough?
+ //for (Variables::const_iterator i = data.begin(); i != data.end(); ++i)
+ // _engine->set_variable(subject, i->first, i->second);
+
+ if (!existing)
+ _engine->set_property(path, "ingen:enabled", (bool)true);
+
+ _load_path_translations.clear();
+
+ return path;
+}
+
+
+/** Build a NodeModel given a pointer to a Node in a patch file.
+ */
+bool
+DeprecatedLoader::load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ xmlChar* key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ string path = "";
+ bool polyphonic = false;
+
+ string plugin_uri;
+
+ string plugin_type; // deprecated
+ string library_name; // deprecated
+ string plugin_label; // deprecated
+
+ GraphObject::Variables initial_data;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ path = parent.base() + nameify_if_invalid((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) {
+ polyphonic = !strcmp((char*)key, "true");
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) {
+ plugin_type = (const char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) {
+ library_name = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) {
+ plugin_label = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) {
+ plugin_uri = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) {
+ cerr << "FIXME: load port\n";
+#if 0
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string port_name;
+ float user_min = 0.0;
+ float user_max = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"name"))) {
+ port_name = nameify_if_invalid((char*)key);
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-min"))) {
+ user_min = atof((char*)key);
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-max"))) {
+ user_max = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ assert(path.length() > 0);
+ assert(Path::is_valid(path));
+
+ // FIXME: /nasty/ assumptions
+ SharedPtr<PortModel> pm(new PortModel(Path(path).base() + port_name,
+ PortModel::CONTROL, PortModel::INPUT, PortModel::NONE,
+ 0.0, user_min, user_max));
+ //pm->set_parent(nm);
+ nm->add_port(pm);
+#endif
+
+ } else { // Don't know what this tag is, add it as variable
+ if (key)
+ add_variable(initial_data, (const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ if (path == "") {
+ cerr << "[DeprecatedLoader] Malformed patch file (node tag has empty children)" << endl;
+ cerr << "[DeprecatedLoader] Node ignored." << endl;
+ return false;
+ }
+
+ // Compatibility hacks for old patches that represent patch ports as nodes
+ if (plugin_uri == "") {
+ bool is_port = false;
+
+ if (plugin_type == "Internal") {
+ // FIXME: indices
+ if (plugin_label == "audio_input") {
+ _engine->new_port(path, 0, "ingen:AudioPort", false);
+ is_port = true;
+ } else if (plugin_label == "audio_output") {
+ _engine->new_port(path, 0, "ingen:AudioPort", true);
+ is_port = true;
+ } else if (plugin_label == "control_input") {
+ _engine->new_port(path, 0, "ingen:ControlPort", false);
+ is_port = true;
+ } else if (plugin_label == "control_output" ) {
+ _engine->new_port(path, 0, "ingen:ControlPort", true);
+ is_port = true;
+ } else if (plugin_label == "midi_input") {
+ _engine->new_port(path, 0, "ingen:MIDIPort", false);
+ is_port = true;
+ } else if (plugin_label == "midi_output" ) {
+ _engine->new_port(path, 0, "ingen:MIDIPort", true);
+ is_port = true;
+ } else {
+ cerr << "WARNING: Unknown internal plugin label \"" << plugin_label << "\"" << endl;
+ }
+ }
+
+ if (is_port) {
+ const string old_path = path;
+ const string new_path = (Path::is_valid(old_path) ? old_path : Path::pathify(old_path));
+
+ if (!Path::is_valid(old_path))
+ cerr << "WARNING: Translating invalid port path \"" << old_path << "\" => \""
+ << new_path << "\"" << endl;
+
+ // Set up translations (for connections etc) to alias both the old
+ // module path and the old module/port path to the new port path
+ _load_path_translations[old_path] = new_path;
+ _load_path_translations[old_path + "/in"] = new_path;
+ _load_path_translations[old_path + "/out"] = new_path;
+
+ path = new_path;
+
+ for (GraphObject::Variables::const_iterator i = initial_data.begin(); i != initial_data.end(); ++i)
+ _engine->set_variable(path, i->first, i->second);
+
+ return SharedPtr<NodeModel>();
+
+ } else {
+ if (plugin_label == "note_in") {
+ plugin_uri = NS_INGEN "note_node";
+ } else if (plugin_label == "control_input") {
+ plugin_uri = NS_INGEN "control_node";
+ } else if (plugin_label == "transport") {
+ plugin_uri = NS_INGEN "transport_node";
+ } else if (plugin_label == "trigger_in") {
+ plugin_uri = NS_INGEN "trigger_node";
+ } else {
+ cerr << "WARNING: Unknown deprecated node (label " << plugin_label
+ << ")." << endl;
+ }
+
+ if (plugin_uri != "")
+ _engine->new_node(path, plugin_uri);
+ else
+ _engine->new_node_deprecated(path, plugin_type, library_name, plugin_label);
+
+ _engine->set_property(path, "ingen:polyphonic", polyphonic);
+
+ for (GraphObject::Variables::const_iterator i = initial_data.begin(); i != initial_data.end(); ++i)
+ _engine->set_variable(path, i->first, i->second);
+
+ return true;
+ }
+
+ // Not deprecated
+ } else {
+ _engine->new_node(path, plugin_uri);
+ _engine->set_property(path, "ingen:polyphonic", polyphonic);
+ for (GraphObject::Variables::const_iterator i = initial_data.begin(); i != initial_data.end(); ++i)
+ _engine->set_variable(path, i->first, i->second);
+ return true;
+ }
+
+ // (shouldn't get here)
+}
+
+
+bool
+DeprecatedLoader::load_subpatch(const string& base_filename, const Path& parent, xmlDocPtr doc, const xmlNodePtr subpatch)
+{
+ xmlChar *key;
+ xmlNodePtr cur = subpatch->xmlChildrenNode;
+
+ string name = "";
+ string filename = "";
+ size_t poly = 0;
+
+ GraphObject::Variables initial_data;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ name = (const char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ initial_data.insert(make_pair("ingen::polyphony", (int)poly));
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) {
+ filename = base_filename + "/" + (const char*)key;
+ } else { // Don't know what this tag is, add it as variable
+ if (key != NULL && strlen((const char*)key) > 0)
+ add_variable(initial_data, (const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ cout << "Loading subpatch " << filename << " under " << parent << endl;
+ // load_patch sets the passed variable last, so values stored in the parent
+ // will override values stored in the child patch file
+ /*string path = */load_patch(filename, parent, name, initial_data, false);
+
+ return false;
+}
+
+
+/** Build a ConnectionModel given a pointer to a connection in a patch file.
+ */
+bool
+DeprecatedLoader::load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ xmlChar *key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ string source_node, source_port, dest_node, dest_port;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-node"))) {
+ source_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-port"))) {
+ source_port = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-node"))) {
+ dest_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-port"))) {
+ dest_port = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (source_node == "" || source_port == "" || dest_node == "" || dest_port == "") {
+ cerr << "ERROR: Malformed patch file (connection tag has empty children)" << endl;
+ cerr << "ERROR: Connection ignored." << endl;
+ return false;
+ }
+
+ // Compatibility fixes for old (fundamentally broken) patches
+ source_node = nameify_if_invalid(source_node);
+ source_port = nameify_if_invalid(source_port);
+ dest_node = nameify_if_invalid(dest_node);
+ dest_port = nameify_if_invalid(dest_port);
+
+ _engine->connect(
+ translate_load_path(parent.base() + source_node +"/"+ source_port),
+ translate_load_path(parent.base() + dest_node +"/"+ dest_port));
+
+ return true;
+}
+
+
+/** Build a PresetModel given a pointer to a preset in a patch file.
+ */
+SharedPtr<PresetModel>
+DeprecatedLoader::load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ xmlNodePtr cur = node->xmlChildrenNode;
+ xmlChar* key;
+
+ SharedPtr<PresetModel> pm(new PresetModel(parent.base()));
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ assert(key != NULL);
+ pm->name((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"control"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string node_name = "", port_name = "";
+ float val = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"node-name"))) {
+ node_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"port-name"))) {
+ port_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
+ val = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ // Compatibility fixes for old patch files
+ if (node_name != "")
+ node_name = nameify_if_invalid(node_name);
+ port_name = nameify_if_invalid(port_name);
+
+ if (port_name == "") {
+ string msg = "Unable to parse control in patch file ( node = ";
+ msg.append(node_name).append(", port = ").append(port_name).append(")");
+ cerr << "ERROR: " << msg << endl;
+ //m_client_hooks->error(msg);
+ } else {
+ // FIXME: temporary compatibility, remove any slashes from port name
+ // remove this soon once patches have migrated
+ string::size_type slash_index;
+ while ((slash_index = port_name.find("/")) != string::npos)
+ port_name[slash_index] = '-';
+
+ pm->add_control(node_name, port_name, val);
+ }
+ }
+ xmlFree(key);
+ key = NULL;
+ cur = cur->next;
+ }
+ if (pm->name() == "") {
+ cerr << "Preset in patch file has no name." << endl;
+ //m_client_hooks->error("Preset in patch file has no name.");
+ pm->name("Unnamed");
+ }
+
+ return pm;
+}
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/DeprecatedLoader.hpp b/src/client/DeprecatedLoader.hpp
new file mode 100644
index 00000000..c1af52c2
--- /dev/null
+++ b/src/client/DeprecatedLoader.hpp
@@ -0,0 +1,94 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 PATCHLIBRARIAN_H
+#define PATCHLIBRARIAN_H
+
+#include <map>
+#include <utility>
+#include <string>
+#include <cassert>
+#include <boost/optional.hpp>
+#include <glibmm/ustring.h>
+#include <libxml/tree.h>
+#include <raul/SharedPtr.hpp>
+#include <raul/Path.hpp>
+#include "interface/EngineInterface.hpp"
+#include "interface/GraphObject.hpp"
+#include "ObjectModel.hpp"
+
+using std::string;
+using Ingen::Shared::EngineInterface;
+using Ingen::Shared::GraphObject;
+
+namespace Ingen {
+namespace Client {
+
+class PatchModel;
+class NodeModel;
+class ConnectionModel;
+class PresetModel; // defined in DeprecatedLoader.cpp
+
+
+/** Loads deprecated (XML) patch files (from the Om days).
+ *
+ * \ingroup IngenClient
+ */
+class DeprecatedLoader
+{
+public:
+ DeprecatedLoader(SharedPtr<EngineInterface> engine)
+ : /*_patch_search_path(".")*/ _engine(engine)
+ {
+ assert(_engine);
+ }
+
+ /*void path(const string& path) { _patch_search_path = path; }
+ const string& path() { return _patch_search_path; }*/
+
+ string find_file(const string& filename, const string& additional_path = "");
+
+ string load_patch(const Glib::ustring& filename,
+ boost::optional<Path> parent_path,
+ string name,
+ GraphObject::Variables initial_data,
+ bool existing = false);
+
+private:
+ void add_variable(GraphObject::Variables& data, string key, string value);
+
+ string nameify_if_invalid(const string& name);
+ string translate_load_path(const string& path);
+
+ //string _patch_search_path;
+ SharedPtr<EngineInterface> _engine;
+
+ /// Translations of paths from the loading file to actual paths (for deprecated patches)
+ std::map<string, string> _load_path_translations;
+
+ bool load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur);
+ bool load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur);
+ bool load_subpatch(const string& base_filename, const Path& parent, xmlDocPtr doc, const xmlNodePtr cur);
+
+ SharedPtr<PresetModel> load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur);
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // PATCHLIBRARIAN_H
diff --git a/src/client/HTTPClientReceiver.cpp b/src/client/HTTPClientReceiver.cpp
new file mode 100644
index 00000000..ece55ab2
--- /dev/null
+++ b/src/client/HTTPClientReceiver.cpp
@@ -0,0 +1,97 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave 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 <list>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+#include <raul/AtomLiblo.hpp>
+#include "module/Module.hpp"
+#include "HTTPClientReceiver.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Client {
+
+
+HTTPClientReceiver::HTTPClientReceiver(
+ Shared::World* world,
+ const std::string& url,
+ SharedPtr<Shared::ClientInterface> target)
+ : _target(target)
+ , _world(world)
+ , _url(url)
+ , _session(NULL)
+{
+ start(false);
+}
+
+
+HTTPClientReceiver::~HTTPClientReceiver()
+{
+ stop();
+}
+
+
+void
+HTTPClientReceiver::message_callback(SoupSession* session, SoupMessage* msg, void* ptr)
+{
+ HTTPClientReceiver* me = (HTTPClientReceiver*)ptr;
+ cout << "RECEIVED ASYNC MESSAGE: " << msg->response_body->data << endl;
+ me->_target->response_ok(0);
+ me->_target->enable();
+ me->_parser->parse_string(me->_world, me->_target.get(), Glib::ustring(msg->response_body->data),
+ Glib::ustring("/"), Glib::ustring(""));
+}
+
+
+void
+HTTPClientReceiver::start(bool dump)
+{
+ Glib::Mutex::Lock lock(_world->rdf_world->mutex());
+ if (!_parser) {
+ if (!_world->serialisation_module)
+ _world->serialisation_module = Ingen::Shared::load_module("ingen_serialisation");
+
+ if (_world->serialisation_module) {
+ Parser* (*new_parser)() = NULL;
+ if (_world->serialisation_module->get_symbol("new_parser", (void*&)new_parser))
+ _parser = SharedPtr<Parser>(new_parser());
+ }
+ }
+ _session = soup_session_async_new();
+ SoupMessage* msg = soup_message_new("GET", _url.c_str());
+ soup_session_queue_message (_session, msg, message_callback, this);
+}
+
+
+void
+HTTPClientReceiver::stop()
+{
+ if (_session != NULL) {
+ //unregister_client();
+ soup_session_abort(_session);
+ _session = NULL;
+ }
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/HTTPClientReceiver.hpp b/src/client/HTTPClientReceiver.hpp
new file mode 100644
index 00000000..bab55578
--- /dev/null
+++ b/src/client/HTTPClientReceiver.hpp
@@ -0,0 +1,62 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave 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 HTTPCLIENTRECEIVER_H
+#define HTTPCLIENTRECEIVER_H
+
+#include <cstdlib>
+#include <boost/utility.hpp>
+#include <libsoup/soup.h>
+#include "interface/ClientInterface.hpp"
+#include "serialisation/Parser.hpp"
+#include "redlandmm/World.hpp"
+#include "raul/Deletable.hpp"
+
+namespace Ingen {
+namespace Client {
+
+
+class HTTPClientReceiver : public boost::noncopyable, public Raul::Deletable
+{
+public:
+ HTTPClientReceiver(Shared::World* world,
+ const std::string& url,
+ SharedPtr<Shared::ClientInterface> target);
+
+ ~HTTPClientReceiver();
+
+ std::string uri() const { return _url; }
+
+ void start(bool dump);
+ void stop();
+
+private:
+ static void message_callback(SoupSession* session, SoupMessage* msg, void* ptr);
+
+ SharedPtr<Shared::ClientInterface> _target;
+
+ Shared::World* _world;
+ const std::string _url;
+ SoupSession* _session;
+ SharedPtr<Parser> _parser;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // HTTPCLIENTRECEIVER_H
diff --git a/src/client/HTTPEngineSender.cpp b/src/client/HTTPEngineSender.cpp
new file mode 100644
index 00000000..733e0ac7
--- /dev/null
+++ b/src/client/HTTPEngineSender.cpp
@@ -0,0 +1,285 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave 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 <iostream>
+#include <libsoup/soup.h>
+#include "HTTPEngineSender.hpp"
+
+using namespace std;
+
+namespace Ingen {
+namespace Client {
+
+
+HTTPEngineSender::HTTPEngineSender(const string& engine_url)
+ : _engine_url(engine_url)
+ , _id(0)
+ , _enabled(true)
+{
+ _session = soup_session_sync_new();
+}
+
+
+HTTPEngineSender::~HTTPEngineSender()
+{
+ soup_session_abort(_session);
+}
+
+
+void
+HTTPEngineSender::attach(int32_t ping_id, bool block)
+{
+ /*SoupMessage *msg;
+ msg = soup_message_new ("GET", _engine_url.c_str());
+ int status = soup_session_send_message (_session, msg);
+ cout << "STATUS: " << status << endl;
+ cout << "RESPONSE: " << msg->response_body->data << endl;*/
+}
+
+
+/* *** EngineInterface implementation below here *** */
+
+
+/** Register with the engine via HTTP.
+ *
+ * Note that this does not actually use 'key', since the engine creates
+ * it's own key for HTTP clients (namely the incoming URL), for NAT
+ * traversal. It is a parameter to remain compatible with EngineInterface.
+ */
+void
+HTTPEngineSender::register_client(ClientInterface* client)
+{
+
+}
+
+
+void
+HTTPEngineSender::unregister_client(const string& uri)
+{
+
+}
+
+
+// Engine commands
+void
+HTTPEngineSender::load_plugins()
+{
+
+}
+
+
+void
+HTTPEngineSender::activate()
+{
+
+}
+
+
+void
+HTTPEngineSender::deactivate()
+{
+
+}
+
+
+void
+HTTPEngineSender::quit()
+{
+
+}
+
+
+
+// Object commands
+
+void
+HTTPEngineSender::new_patch(const string& path,
+ uint32_t poly)
+{
+}
+
+
+void
+HTTPEngineSender::new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool is_output)
+{
+}
+
+
+void
+HTTPEngineSender::new_node(const string& path,
+ const string& plugin_uri)
+{
+}
+
+
+/** Create a node using library name and plugin label (DEPRECATED).
+ *
+ * DO NOT USE THIS.
+ */
+void
+HTTPEngineSender::new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& library_name,
+ const string& plugin_label)
+{
+}
+
+
+void
+HTTPEngineSender::rename(const string& old_path,
+ const string& new_name)
+{
+}
+
+
+void
+HTTPEngineSender::destroy(const string& path)
+{
+}
+
+
+void
+HTTPEngineSender::clear_patch(const string& patch_path)
+{
+}
+
+
+void
+HTTPEngineSender::connect(const string& src_port_path,
+ const string& dst_port_path)
+{
+}
+
+
+void
+HTTPEngineSender::disconnect(const string& src_port_path,
+ const string& dst_port_path)
+{
+}
+
+
+void
+HTTPEngineSender::disconnect_all(const string& parent_patch_path,
+ const string& node_path)
+{
+}
+
+
+void
+HTTPEngineSender::set_port_value(const string& port_path,
+ const Raul::Atom& value)
+{
+}
+
+
+void
+HTTPEngineSender::set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value)
+{
+}
+
+
+void
+HTTPEngineSender::set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program)
+{
+}
+
+
+void
+HTTPEngineSender::midi_learn(const string& node_path)
+{
+}
+
+
+void
+HTTPEngineSender::set_variable(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value)
+{
+}
+
+
+void
+HTTPEngineSender::set_property(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value)
+{
+}
+
+
+
+// Requests //
+
+void
+HTTPEngineSender::ping()
+{
+}
+
+
+void
+HTTPEngineSender::request_plugin(const string& uri)
+{
+}
+
+
+void
+HTTPEngineSender::request_object(const string& path)
+{
+}
+
+
+void
+HTTPEngineSender::request_port_value(const string& port_path)
+{
+}
+
+
+void
+HTTPEngineSender::request_variable(const string& object_path, const string& key)
+{
+}
+
+
+void
+HTTPEngineSender::request_property(const string& object_path, const string& key)
+{
+}
+
+
+void
+HTTPEngineSender::request_plugins()
+{
+}
+
+
+void
+HTTPEngineSender::request_all_objects()
+{
+}
+
+
+
+} // namespace Client
+} // namespace Ingen
+
+
diff --git a/src/client/HTTPEngineSender.hpp b/src/client/HTTPEngineSender.hpp
new file mode 100644
index 00000000..411ddfd5
--- /dev/null
+++ b/src/client/HTTPEngineSender.hpp
@@ -0,0 +1,155 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave 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 HTTPENGINESENDER_H
+#define HTTPENGINESENDER_H
+
+#include <inttypes.h>
+#include <string>
+#include <libsoup/soup.h>
+#include "interface/EngineInterface.hpp"
+using std::string;
+using Ingen::Shared::EngineInterface;
+using Ingen::Shared::ClientInterface;
+
+namespace Ingen {
+namespace Client {
+
+
+/* HTTP (via libsoup) interface to the engine.
+ *
+ * Clients can use this opaquely as an EngineInterface to control the engine
+ * over HTTP (whether over a network or not).
+ *
+ * \ingroup IngenClient
+ */
+class HTTPEngineSender : public EngineInterface {
+public:
+ HTTPEngineSender(const string& engine_url);
+ ~HTTPEngineSender();
+
+ string uri() const { return _engine_url; }
+
+ inline int32_t next_id()
+ { int32_t ret = (_id == -1) ? -1 : _id++; return ret; }
+
+ void set_next_response_id(int32_t id) { _id = id; }
+ void disable_responses() { _id = -1; }
+
+ void attach(int32_t ping_id, bool block);
+
+
+ /* *** EngineInterface implementation below here *** */
+
+ void enable() { _enabled = true; }
+ void disable() { _enabled = false; }
+
+ void bundle_begin() { transfer_begin(); }
+ void bundle_end() { transfer_end(); }
+
+ void transfer_begin();
+ void transfer_end();
+
+ // Client registration
+ void register_client(ClientInterface* client);
+ void unregister_client(const string& uri);
+
+ // Engine commands
+ void load_plugins();
+ void activate();
+ void deactivate();
+ void quit();
+
+ // Object commands
+
+ void new_patch(const string& path,
+ uint32_t poly);
+
+ void new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool is_output);
+
+ void new_node(const string& path,
+ const string& plugin_uri);
+
+ void new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& library_name,
+ const string& plugin_label);
+
+ void rename(const string& old_path,
+ const string& new_name);
+
+ void destroy(const string& path);
+
+ void clear_patch(const string& patch_path);
+
+ void connect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect_all(const string& parent_patch_path,
+ const string& node_path);
+
+ void set_port_value(const string& port_path,
+ const Raul::Atom& value);
+
+ void set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value);
+
+ void set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program);
+
+ void midi_learn(const string& node_path);
+
+ void set_variable(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ void set_property(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ // Requests //
+ void ping();
+ void request_plugin(const string& uri);
+ void request_object(const string& path);
+ void request_port_value(const string& port_path);
+ void request_variable(const string& path, const string& key);
+ void request_property(const string& path, const string& key);
+ void request_plugins();
+ void request_all_objects();
+
+protected:
+ SoupSession* _session;
+ const string _engine_url;
+ int _client_port;
+ int32_t _id;
+ bool _enabled;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // HTTPENGINESENDER_H
+
diff --git a/src/client/Makefile.am b/src/client/Makefile.am
new file mode 100644
index 00000000..4af7c243
--- /dev/null
+++ b/src/client/Makefile.am
@@ -0,0 +1,69 @@
+if BUILD_CLIENT_LIB
+
+
+moduledir = $(libdir)/ingen
+
+module_LTLIBRARIES = libingen_client.la
+
+libingen_client_la_CXXFLAGS = \
+ -DPKGDATADIR=\"$(pkgdatadir)\" \
+ @INGEN_CFLAGS@ \
+ @GLIBMM_CFLAGS@ \
+ @LIBLO_CFLAGS@ \
+ @LSIGCPP_CFLAGS@ \
+ @GLIBMM_CFLAGS@ \
+ @LXML2_CFLAGS@ \
+ @RAUL_CFLAGS@ \
+ @REDLANDMM_CFLAGS@ \
+ @SLV2_CFLAGS@ \
+ @SOUP_CFLAGS@
+
+libingen_client_la_LIBADD = \
+ ../shared/libingen_shared.la \
+ @GLIBMM_LIBS@ \
+ @LIBLO_LIBS@ \
+ @LSIGCPP_LIBS@ \
+ @LXML2_LIBS@ \
+ @RAUL_LIBS@ \
+ @REDLANDMM_LIBS@ \
+ @SLV2_LIBS@ \
+ @SOUP_LIBS@
+
+libingen_client_la_SOURCES = \
+ ClientStore.cpp \
+ ClientStore.hpp \
+ ConnectionModel.hpp \
+ DeprecatedLoader.cpp \
+ DeprecatedLoader.hpp \
+ NodeModel.cpp \
+ NodeModel.hpp \
+ OSCClientReceiver.cpp \
+ OSCClientReceiver.hpp \
+ OSCEngineSender.cpp \
+ OSCEngineSender.hpp \
+ ObjectModel.cpp \
+ ObjectModel.hpp \
+ PatchModel.cpp \
+ PatchModel.hpp \
+ PluginModel.cpp \
+ PluginModel.hpp \
+ PluginUI.cpp \
+ PluginUI.hpp \
+ PortModel.cpp \
+ PortModel.hpp \
+ SigClientInterface.hpp \
+ ThreadedSigClientInterface.cpp \
+ ThreadedSigClientInterface.hpp \
+ client.cpp \
+ client.hpp
+
+if WITH_SOUP
+libingen_client_la_SOURCES += \
+ HTTPClientReceiver.cpp \
+ HTTPClientReceiver.hpp \
+ HTTPEngineSender.cpp \
+ HTTPEngineSender.hpp
+endif
+
+endif # BUILD_CLIENT_LIB
+
diff --git a/src/client/NodeModel.cpp b/src/client/NodeModel.cpp
new file mode 100644
index 00000000..ac0c8e68
--- /dev/null
+++ b/src/client/NodeModel.cpp
@@ -0,0 +1,224 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 CONFIG_H_PATH
+
+#include <cassert>
+#include <cmath>
+#include "interface/Port.hpp"
+#include "NodeModel.hpp"
+#include "PatchModel.hpp"
+
+namespace Ingen {
+namespace Client {
+
+
+NodeModel::NodeModel(SharedPtr<PluginModel> plugin, const Path& path)
+ : ObjectModel(path)
+ , _plugin_uri(plugin->uri())
+ , _plugin(plugin)
+ , _num_values(0)
+ , _min_values(0)
+ , _max_values(0)
+{
+}
+
+NodeModel::NodeModel(const string& plugin_uri, const Path& path)
+ : ObjectModel(path)
+ , _plugin_uri(plugin_uri)
+ , _num_values(0)
+ , _min_values(0)
+ , _max_values(0)
+{
+}
+
+
+NodeModel::NodeModel(const NodeModel& copy)
+ : ObjectModel(copy)
+ , _plugin_uri(copy._plugin_uri)
+ , _num_values(copy._num_values)
+ , _min_values((float*)malloc(sizeof(float) * _num_values))
+ , _max_values((float*)malloc(sizeof(float) * _num_values))
+{
+ memcpy(_min_values, copy._min_values, sizeof(float) * _num_values);
+ memcpy(_max_values, copy._max_values, sizeof(float) * _num_values);
+}
+
+
+NodeModel::~NodeModel()
+{
+ clear();
+}
+
+
+void
+NodeModel::remove_port(SharedPtr<PortModel> port)
+{
+ // FIXME: slow
+ for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) {
+ if ((*i) == port) {
+ _ports.erase(i);
+ break;
+ }
+ }
+ signal_removed_port.emit(port);
+}
+
+
+void
+NodeModel::remove_port(const Path& port_path)
+{
+ // FIXME: slow
+ for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) {
+ if ((*i)->path() == port_path) {
+ _ports.erase(i);
+ break;
+ }
+ }
+}
+
+
+void
+NodeModel::clear()
+{
+ _ports.clear();
+ assert(_ports.empty());
+ delete[] _min_values;
+ delete[] _max_values;
+ _min_values = 0;
+ _max_values = 0;
+}
+
+
+void
+NodeModel::add_child(SharedPtr<ObjectModel> c)
+{
+ assert(c->parent().get() == this);
+
+ //ObjectModel::add_child(c);
+
+ SharedPtr<PortModel> pm = PtrCast<PortModel>(c);
+ assert(pm);
+ add_port(pm);
+}
+
+
+bool
+NodeModel::remove_child(SharedPtr<ObjectModel> c)
+{
+ assert(c->path().is_child_of(_path));
+ assert(c->parent().get() == this);
+
+ //bool ret = ObjectModel::remove_child(c);
+
+ SharedPtr<PortModel> pm = PtrCast<PortModel>(c);
+ assert(pm);
+ remove_port(pm);
+
+ //return ret;
+ return true;
+}
+
+
+void
+NodeModel::add_port(SharedPtr<PortModel> pm)
+{
+ assert(pm);
+ assert(pm->path().is_child_of(_path));
+ assert(pm->parent().get() == this);
+
+ Ports::iterator existing = find(_ports.begin(), _ports.end(), pm);
+
+ // Store should have handled this by merging the two
+ assert(existing == _ports.end());
+
+ _ports.push_back(pm);
+ signal_new_port.emit(pm);
+}
+
+
+SharedPtr<PortModel>
+NodeModel::get_port(const string& port_name) const
+{
+ assert(port_name.find("/") == string::npos);
+ for (Ports::const_iterator i = _ports.begin(); i != _ports.end(); ++i)
+ if ((*i)->path().name() == port_name)
+ return (*i);
+ return SharedPtr<PortModel>();
+}
+
+
+Shared::Port*
+NodeModel::port(uint32_t index) const
+{
+ assert(index < num_ports());
+ return dynamic_cast<Shared::Port*>(_ports[index].get());
+}
+
+
+void
+NodeModel::port_value_range(SharedPtr<PortModel> port, float& min, float& max)
+{
+ assert(port->parent().get() == this);
+
+#ifdef HAVE_SLV2
+ // Plugin value first
+ if (_plugin && _plugin->type() == PluginModel::LV2) {
+
+ if (!_min_values) {
+
+ Glib::Mutex::Lock lock(PluginModel::rdf_world()->mutex());
+
+ _num_values = slv2_plugin_get_num_ports(_plugin->slv2_plugin());
+ _min_values = new float[_num_values];
+ _max_values = new float[_num_values];
+ slv2_plugin_get_port_ranges_float(_plugin->slv2_plugin(),
+ _min_values, _max_values, 0);
+ }
+
+ if (!std::isnan(_min_values[port->index()]))
+ min = _min_values[port->index()];
+ if (!std::isnan(_max_values[port->index()]))
+ max = _max_values[port->index()];
+ }
+#endif
+
+ // Possibly overriden
+ const Atom& min_atom = port->get_variable("ingen:minimum");
+ const Atom& max_atom = port->get_variable("ingen:maximum");
+ if (min_atom.type() == Atom::FLOAT)
+ min = min_atom.get_float();
+ if (max_atom.type() == Atom::FLOAT)
+ max = max_atom.get_float();
+}
+
+
+void
+NodeModel::set(SharedPtr<ObjectModel> model)
+{
+ SharedPtr<NodeModel> node = PtrCast<NodeModel>(model);
+ if (node) {
+ _plugin_uri = node->_plugin_uri;
+ _plugin = node->_plugin;
+ }
+
+ ObjectModel::set(model);
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/NodeModel.hpp b/src/client/NodeModel.hpp
new file mode 100644
index 00000000..03afc17c
--- /dev/null
+++ b/src/client/NodeModel.hpp
@@ -0,0 +1,101 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 NODEMODEL_H
+#define NODEMODEL_H
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <sigc++/sigc++.h>
+#include <raul/Table.hpp>
+#include <raul/Path.hpp>
+#include <raul/SharedPtr.hpp>
+#include "interface/Node.hpp"
+#include "interface/Port.hpp"
+#include "ObjectModel.hpp"
+#include "PortModel.hpp"
+#include "PluginModel.hpp"
+
+using std::string;
+using Raul::Table;
+
+namespace Ingen {
+namespace Client {
+
+class PluginModel;
+class ClientStore;
+
+
+/** Node model class, used by the client to store engine's state.
+ *
+ * \ingroup IngenClient
+ */
+class NodeModel : public ObjectModel, virtual public Ingen::Shared::Node
+{
+public:
+ NodeModel(const NodeModel& copy);
+ virtual ~NodeModel();
+
+ typedef vector<SharedPtr<PortModel> > Ports;
+
+ SharedPtr<PortModel> get_port(const string& port_name) const;
+
+ Shared::Port* port(uint32_t index) const;
+
+ const string& plugin_uri() const { return _plugin_uri; }
+ const Shared::Plugin* plugin() const { return _plugin.get(); }
+ uint32_t num_ports() const { return _ports.size(); }
+ const Ports& ports() const { return _ports; }
+
+ void port_value_range(SharedPtr<PortModel> port, float& min, float& max);
+
+ // Signals
+ sigc::signal<void, SharedPtr<PortModel> > signal_new_port;
+ sigc::signal<void, SharedPtr<PortModel> > signal_removed_port;
+
+protected:
+ friend class ClientStore;
+
+ NodeModel(const string& plugin_uri, const Path& path);
+ NodeModel(SharedPtr<PluginModel> plugin, const Path& path);
+
+ NodeModel(const Path& path);
+ void add_child(SharedPtr<ObjectModel> c);
+ bool remove_child(SharedPtr<ObjectModel> c);
+ void add_port(SharedPtr<PortModel> pm);
+ void remove_port(SharedPtr<PortModel> pm);
+ void remove_port(const Path& port_path);
+ void add_program(int bank, int program, const string& name);
+ void remove_program(int bank, int program);
+ void set(SharedPtr<ObjectModel> model);
+
+ virtual void clear();
+
+ Ports _ports; ///< Vector of ports (not a Table to preserve order)
+ string _plugin_uri; ///< Plugin URI (if PluginModel is unknown)
+ SharedPtr<PluginModel> _plugin; ///< The plugin this node is an instance of
+ uint32_t _num_values; ///< Size of _min_values and _max_values
+ float* _min_values; ///< Port min values (cached for LV2)
+ float* _max_values; ///< Port max values (cached for LV2)
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // NODEMODEL_H
diff --git a/src/client/OSCClientReceiver.cpp b/src/client/OSCClientReceiver.cpp
new file mode 100644
index 00000000..8ebd9d8e
--- /dev/null
+++ b/src/client/OSCClientReceiver.cpp
@@ -0,0 +1,394 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 "OSCClientReceiver.hpp"
+#include <raul/AtomLiblo.hpp>
+#include <list>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Client {
+
+
+OSCClientReceiver::OSCClientReceiver(int listen_port, SharedPtr<Shared::ClientInterface> target)
+ : _target(target)
+ , _listen_port(listen_port)
+ , _st(NULL)
+{
+ start(false); // true = dump, false = shutup
+}
+
+
+OSCClientReceiver::~OSCClientReceiver()
+{
+ stop();
+}
+
+
+void
+OSCClientReceiver::start(bool dump_osc)
+{
+ if (_st != NULL)
+ return;
+
+ // Attempt preferred port
+ if (_listen_port != 0) {
+ char port_str[8];
+ snprintf(port_str, 8, "%d", _listen_port);
+ _st = lo_server_thread_new(port_str, lo_error_cb);
+ }
+
+ // Find a free port
+ if (!_st) {
+ _st = lo_server_thread_new(NULL, lo_error_cb);
+ _listen_port = lo_server_thread_get_port(_st);
+ }
+
+ if (_st == NULL) {
+ cerr << "[OSCClientReceiver] Could not start OSC listener. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[OSCClientReceiver] Started OSC listener on port " << lo_server_thread_get_port(_st) << endl;
+ }
+
+ // Print all incoming messages
+ if (dump_osc)
+ lo_server_thread_add_method(_st, NULL, NULL, generic_cb, NULL);
+
+ setup_callbacks();
+
+ // Display any uncaught messages to the console
+ //lo_server_thread_add_method(_st, NULL, NULL, unknown_cb, NULL);
+
+ lo_server_thread_start(_st);
+}
+
+
+void
+OSCClientReceiver::stop()
+{
+ if (_st != NULL) {
+ //unregister_client();
+ lo_server_thread_free(_st);
+ _st = NULL;
+ }
+}
+
+
+int
+OSCClientReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data)
+{
+ printf("[OSCMsg] %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");
+
+ /*for (int i=0; i < argc; ++i) {
+ printf(" '%c' ", types[i]);
+ lo_arg_pp(lo_type(types[i]), argv[i]);
+ printf("\n");
+ }
+ printf("\n");*/
+
+ return 1; // not handled
+}
+
+
+void
+OSCClientReceiver::lo_error_cb(int num, const char* msg, const char* path)
+{
+ cerr << "Got error from server: " << msg << endl;
+}
+
+
+
+int
+OSCClientReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data)
+{
+ std::string msg = "Received unknown OSC message: ";
+ msg += path;
+
+ cerr << msg << endl;
+
+ return 0;
+}
+
+
+void
+OSCClientReceiver::setup_callbacks()
+{
+ lo_server_thread_add_method(_st, "/ingen/ok", "i", response_ok_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/error", "is", response_error_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/plugin", "ssss", plugin_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/new_patch", "si", new_patch_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/destroyed", "s", destroyed_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/patch_cleared", "s", patch_cleared_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/object_renamed", "ss", object_renamed_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/new_connection", "ss", connection_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/disconnection", "ss", disconnection_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/new_node", "ss", new_node_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/new_port", "sisi", new_port_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/set_variable", NULL, set_variable_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/set_property", NULL, set_property_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/set_port_value", "sf", set_port_value_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/set_voice_value", "sif", set_voice_value_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/port_activity", "s", port_activity_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/program_add", "siis", program_add_cb, this);
+ lo_server_thread_add_method(_st, "/ingen/program_remove", "sii", program_remove_cb, this);
+}
+
+
+/** Catches errors that aren't a direct result of a client request.
+ */
+int
+OSCClientReceiver::_error_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _target->error((char*)argv[0]);
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_new_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _target->new_patch(&argv[0]->s, argv[1]->i); // path, poly
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_destroyed_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _target->destroy((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_patch_cleared_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _target->patch_cleared((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_object_renamed_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ _target->object_renamed((const char*)&argv[0]->s, (const char*)&argv[1]->s);
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_connection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const src_port_path = &argv[0]->s;
+ const char* const dst_port_path = &argv[1]->s;
+
+ _target->connect(src_port_path, dst_port_path);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_disconnection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* src_port_path = &argv[0]->s;
+ const char* dst_port_path = &argv[1]->s;
+
+ _target->disconnect(src_port_path, dst_port_path);
+
+ return 0;
+}
+
+
+/** Notification of a new node creation.
+ */
+int
+OSCClientReceiver::_new_node_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* uri = &argv[0]->s;
+ const char* node_path = &argv[1]->s;
+
+ _target->new_node(uri, node_path);
+
+ return 0;
+}
+
+
+/** Notification of a new port creation.
+ */
+int
+OSCClientReceiver::_new_port_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* port_path = &argv[0]->s;
+ const uint32_t index = argv[1]->i;
+ const char* type = &argv[2]->s;
+ const bool is_output = (argv[3]->i == 1);
+
+ _target->new_port(port_path, index, type, is_output);
+
+ return 0;
+}
+
+
+/** Notification of a new or updated variable.
+ */
+int
+OSCClientReceiver::_set_variable_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ if (argc != 3 || types[0] != 's' || types[1] != 's')
+ return 1;
+
+ const char* obj_path = &argv[0]->s;
+ const char* key = &argv[1]->s;
+
+ Atom value = AtomLiblo::lo_arg_to_atom(types[2], argv[2]);
+
+ _target->set_variable(obj_path, key, value);
+
+ return 0;
+}
+
+
+/** Notification of a new or updated property.
+ */
+int
+OSCClientReceiver::_set_property_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ if (argc != 3 || types[0] != 's' || types[1] != 's')
+ return 1;
+
+ const char* obj_path = &argv[0]->s;
+ const char* key = &argv[1]->s;
+
+ Atom value = AtomLiblo::lo_arg_to_atom(types[2], argv[2]);
+
+ _target->set_property(obj_path, key, value);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_set_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const port_path = &argv[0]->s;
+ const float value = argv[1]->f;
+
+ _target->set_port_value(port_path, value);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_set_voice_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const port_path = &argv[0]->s;
+ const int voice = argv[1]->i;
+ const float value = argv[2]->f;
+
+ _target->set_voice_value(port_path, voice, value);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_port_activity_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const port_path = &argv[0]->s;
+
+ _target->port_activity(port_path);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_response_ok_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ assert(!strcmp(types, "i"));
+ _target->response_ok(argv[0]->i);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_response_error_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ assert(!strcmp(types, "is"));
+ _target->response_error(argv[0]->i, &argv[1]->s);
+
+ return 0;
+}
+
+
+/** A plugin info response from the server, in response to an /ingen/send_plugins
+ */
+int
+OSCClientReceiver::_plugin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ assert(argc == 4 && !strcmp(types, "ssss"));
+ _target->new_plugin(&argv[0]->s, &argv[1]->s, &argv[2]->s, &argv[3]->s); // uri, type, symbol, name
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_program_add_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[0]->s;
+ int32_t bank = argv[1]->i;
+ int32_t program = argv[2]->i;
+ const char* name = &argv[3]->s;
+
+ _target->program_add(node_path, bank, program, name);
+
+ return 0;
+}
+
+
+int
+OSCClientReceiver::_program_remove_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[0]->s;
+ int32_t bank = argv[1]->i;
+ int32_t program = argv[2]->i;
+
+ _target->program_remove(node_path, bank, program);
+
+ return 0;
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/OSCClientReceiver.hpp b/src/client/OSCClientReceiver.hpp
new file mode 100644
index 00000000..ea5871b3
--- /dev/null
+++ b/src/client/OSCClientReceiver.hpp
@@ -0,0 +1,107 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 OSCCLIENTRECEIVER_H
+#define OSCCLIENTRECEIVER_H
+
+#include <cstdlib>
+#include <boost/utility.hpp>
+#include <lo/lo.h>
+#include "interface/ClientInterface.hpp"
+#include "raul/Deletable.hpp"
+
+namespace Ingen {
+namespace Client {
+
+/** Arguments to a liblo handler */
+#define LO_HANDLER_ARGS const char* path, const char* types, lo_arg** argv, int argc, lo_message msg
+
+/** Define 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. */
+#define LO_HANDLER(name) \
+int _##name##_cb (LO_HANDLER_ARGS);\
+inline static int name##_cb(LO_HANDLER_ARGS, void* osc_listener)\
+{ return ((OSCClientReceiver*)osc_listener)->_##name##_cb(path, types, argv, argc, msg); }
+
+
+/** Callbacks for "notification band" OSC messages.
+ *
+ * Receives all notification of engine state, but not replies on the "control
+ * band". See OSC namespace documentation for details.
+ *
+ * Right now this class and Comm share the same lo_server_thread and the barrier
+ * between them is a bit odd, but eventually this class will be able to listen
+ * on a completely different port (ie have it's own lo_server_thread) to allow
+ * things like listening to the notification band over TCP while sending commands
+ * on the control band over UDP.
+ *
+ * \ingroup IngenClient
+ */
+class OSCClientReceiver : public boost::noncopyable, public Raul::Deletable
+{
+public:
+ OSCClientReceiver(int listen_port, SharedPtr<Shared::ClientInterface> target);
+ ~OSCClientReceiver();
+
+ std::string uri() const { return lo_server_thread_get_url(_st); }
+
+ void start(bool dump_osc);
+ void stop();
+
+ int listen_port() { return _listen_port; }
+ std::string listen_url() { return lo_server_thread_get_url(_st); }
+
+private:
+ void setup_callbacks();
+
+ static void lo_error_cb(int num, const char* msg, const char* path);
+
+ static int generic_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data);
+ static int unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* osc_receiver);
+
+ SharedPtr<Shared::ClientInterface> _target;
+
+ int _listen_port;
+ lo_server_thread _st;
+
+ LO_HANDLER(error);
+ LO_HANDLER(response_ok);
+ LO_HANDLER(response_error);
+ LO_HANDLER(plugin);
+ LO_HANDLER(plugin_list_end);
+ LO_HANDLER(new_patch);
+ LO_HANDLER(destroyed);
+ LO_HANDLER(patch_cleared);
+ LO_HANDLER(object_renamed);
+ LO_HANDLER(connection);
+ LO_HANDLER(disconnection);
+ LO_HANDLER(new_node);
+ LO_HANDLER(new_port);
+ LO_HANDLER(set_variable);
+ LO_HANDLER(set_property);
+ LO_HANDLER(set_port_value);
+ LO_HANDLER(set_voice_value);
+ LO_HANDLER(port_activity);
+ LO_HANDLER(program_add);
+ LO_HANDLER(program_remove);
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // OSCCLIENTRECEIVER_H
diff --git a/src/client/OSCEngineSender.cpp b/src/client/OSCEngineSender.cpp
new file mode 100644
index 00000000..c21d16ce
--- /dev/null
+++ b/src/client/OSCEngineSender.cpp
@@ -0,0 +1,420 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <iostream>
+#include <raul/AtomLiblo.hpp>
+#include "OSCEngineSender.hpp"
+
+using namespace std;
+using Raul::Atom;
+
+namespace Ingen {
+namespace Client {
+
+
+/** Note the sending port is implicitly set by liblo, lo_send by default sends
+ * from the most recently created server, so create the OSC listener before
+ * this to have it all happen on the same port. Yeah, this is a big magic :/
+ */
+OSCEngineSender::OSCEngineSender(const string& engine_url)
+ : _engine_url(engine_url)
+ , _id(0)
+{
+ _address = lo_address_new_from_url(engine_url.c_str());
+}
+
+
+OSCEngineSender::~OSCEngineSender()
+{
+ lo_address_free(_address);
+}
+
+
+/** Attempt to connect to the engine (by pinging it).
+ *
+ * This doesn't register a client (or otherwise affect the client/engine state).
+ * To check for success wait for the ping response with id @a ping_id (using the
+ * relevant OSCClientReceiver).
+ *
+ * Passing a client_port of 0 will automatically choose a free port. If the
+ * @a block parameter is true, this function will not return until a connection
+ * has successfully been made.
+ */
+void
+OSCEngineSender::attach(int32_t ping_id, bool block)
+{
+ if (!_address)
+ _address = lo_address_new_from_url(_engine_url.c_str());
+
+ if (_address == NULL) {
+ cerr << "Aborting: Unable to connect to " << _engine_url << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ cout << "[OSCEngineSender] Attempting to contact engine at " << _engine_url << " ..." << endl;
+
+ _id = ping_id;
+ this->ping();
+}
+
+/* *** EngineInterface implementation below here *** */
+
+
+/** Register with the engine via OSC.
+ *
+ * Note that this does not actually use 'key', since the engine creates
+ * it's own key for OSC clients (namely the incoming URL), for NAT
+ * traversal. It is a parameter to remain compatible with EngineInterface.
+ */
+void
+OSCEngineSender::register_client(ClientInterface* client)
+{
+ // FIXME: use parameters.. er, somehow.
+ send("/ingen/register_client", "i", next_id(), LO_ARGS_END, LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::unregister_client(const string& uri)
+{
+ send("/ingen/unregister_client", "i", next_id(), LO_ARGS_END);
+}
+
+
+// Engine commands
+void
+OSCEngineSender::load_plugins()
+{
+ send("/ingen/load_plugins", "i", next_id(), LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::activate()
+{
+ send("/ingen/activate", "i", next_id(), LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::deactivate()
+{
+ send("/ingen/deactivate", "i", next_id(), LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::quit()
+{
+ send("/ingen/quit", "i", next_id(), LO_ARGS_END);
+}
+
+
+
+// Object commands
+
+void
+OSCEngineSender::new_patch(const string& path,
+ uint32_t poly)
+{
+ send("/ingen/new_patch", "isi",
+ next_id(),
+ path.c_str(),
+ poly,
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool is_output)
+{
+ // FIXME: use index
+ send("/ingen/new_port", "issi",
+ next_id(),
+ path.c_str(),
+ data_type.c_str(),
+ (is_output ? 1 : 0),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::new_node(const string& path,
+ const string& plugin_uri)
+{
+
+ send("/ingen/new_node", "iss",
+ next_id(),
+ path.c_str(),
+ plugin_uri.c_str(),
+ LO_ARGS_END);
+}
+
+
+/** Create a node using library name and plugin label (DEPRECATED).
+ *
+ * DO NOT USE THIS.
+ */
+void
+OSCEngineSender::new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& library_name,
+ const string& plugin_label)
+{
+ send("/ingen/new_node", "issss",
+ next_id(),
+ path.c_str(),
+ plugin_type.c_str(),
+ library_name.c_str(),
+ plugin_label.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::rename(const string& old_path,
+ const string& new_name)
+{
+ send("/ingen/rename", "iss",
+ next_id(),
+ old_path.c_str(),
+ new_name.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::destroy(const string& path)
+{
+ send("/ingen/destroy", "is",
+ next_id(),
+ path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::clear_patch(const string& patch_path)
+{
+ send("/ingen/clear_patch", "is",
+ next_id(),
+ patch_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::connect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ send("/ingen/connect", "iss",
+ next_id(),
+ src_port_path.c_str(),
+ dst_port_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::disconnect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ send("/ingen/disconnect", "iss",
+ next_id(),
+ src_port_path.c_str(),
+ dst_port_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::disconnect_all(const string& parent_patch_path,
+ const string& node_path)
+{
+ send("/ingen/disconnect_all", "iss",
+ next_id(),
+ parent_patch_path.c_str(),
+ node_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::set_port_value(const string& port_path,
+ const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_int32(m, next_id());
+ lo_message_add_string(m, port_path.c_str());
+ if (value.type() == Atom::BLOB)
+ lo_message_add_string(m, value.get_blob_type());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_port_value", m);
+}
+
+
+void
+OSCEngineSender::set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_int32(m, next_id());
+ lo_message_add_string(m, port_path.c_str());
+ lo_message_add_int32(m, voice);
+ if (value.type() == Atom::BLOB)
+ lo_message_add_string(m, value.get_blob_type());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_port_value", m);
+}
+
+
+void
+OSCEngineSender::set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program)
+{
+ send((string("/dssi") + node_path + "/program").c_str(),
+ "ii",
+ bank,
+ program,
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::midi_learn(const string& node_path)
+{
+ send("/ingen/midi_learn", "is",
+ next_id(),
+ node_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::set_variable(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_int32(m, next_id());
+ lo_message_add_string(m, obj_path.c_str());
+ lo_message_add_string(m, predicate.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_variable", m);
+}
+
+
+void
+OSCEngineSender::set_property(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_int32(m, next_id());
+ lo_message_add_string(m, obj_path.c_str());
+ lo_message_add_string(m, predicate.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_property", m);
+}
+
+
+
+// Requests //
+
+void
+OSCEngineSender::ping()
+{
+ send("/ingen/ping", "i", next_id(), LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_plugin(const string& uri)
+{
+ send("/ingen/request_plugin", "is",
+ next_id(),
+ uri.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_object(const string& path)
+{
+ send("/ingen/request_object", "is",
+ next_id(),
+ path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_port_value(const string& port_path)
+{
+ send("/ingen/request_port_value", "is",
+ next_id(),
+ port_path.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_variable(const string& object_path, const string& key)
+{
+ send("/ingen/request_variable", "iss",
+ next_id(),
+ object_path.c_str(),
+ key.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_property(const string& object_path, const string& key)
+{
+ send("/ingen/request_property", "iss",
+ next_id(),
+ object_path.c_str(),
+ key.c_str(),
+ LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_plugins()
+{
+ send("/ingen/request_plugins", "i", next_id(), LO_ARGS_END);
+}
+
+
+void
+OSCEngineSender::request_all_objects()
+{
+ send("/ingen/request_all_objects", "i", next_id(), LO_ARGS_END);
+}
+
+
+
+} // namespace Client
+} // namespace Ingen
+
+
diff --git a/src/client/OSCEngineSender.hpp b/src/client/OSCEngineSender.hpp
new file mode 100644
index 00000000..ef4a2fa3
--- /dev/null
+++ b/src/client/OSCEngineSender.hpp
@@ -0,0 +1,154 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 OSCENGINESENDER_H
+#define OSCENGINESENDER_H
+
+#include <inttypes.h>
+#include <string>
+#include <lo/lo.h>
+#include "interface/EngineInterface.hpp"
+#include "shared/OSCSender.hpp"
+using std::string;
+using Ingen::Shared::EngineInterface;
+using Ingen::Shared::ClientInterface;
+
+namespace Ingen {
+namespace Client {
+
+
+/* OSC (via liblo) interface to the engine.
+ *
+ * Clients can use this opaquely as an EngineInterface* to control the engine
+ * over OSC (whether over a network or not, etc).
+ *
+ * \ingroup IngenClient
+ */
+class OSCEngineSender : public EngineInterface, public Shared::OSCSender {
+public:
+ OSCEngineSender(const string& engine_url);
+
+ ~OSCEngineSender();
+
+ std::string uri() const { return _engine_url; }
+
+ inline int32_t next_id()
+ { int32_t ret = (_id == -1) ? -1 : _id++; return ret; }
+
+ void set_next_response_id(int32_t id) { _id = id; }
+ void disable_responses() { _id = -1; }
+
+ void attach(int32_t ping_id, bool block);
+
+
+ /* *** EngineInterface implementation below here *** */
+
+ void enable() { _enabled = true; }
+ void disable() { _enabled = false; }
+
+ void bundle_begin() { OSCSender::bundle_begin(); }
+ void bundle_end() { OSCSender::bundle_end(); }
+ void transfer_begin() { OSCSender::transfer_begin(); }
+ void transfer_end() { OSCSender::transfer_end(); }
+
+ // Client registration
+ void register_client(ClientInterface* client);
+ void unregister_client(const string& uri);
+
+ // Engine commands
+ void load_plugins();
+ void activate();
+ void deactivate();
+ void quit();
+
+ // Object commands
+
+ void new_patch(const string& path,
+ uint32_t poly);
+
+ void new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool is_output);
+
+ void new_node(const string& path,
+ const string& plugin_uri);
+
+ void new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& library_name,
+ const string& plugin_label);
+
+ void rename(const string& old_path,
+ const string& new_name);
+
+ void destroy(const string& path);
+
+ void clear_patch(const string& patch_path);
+
+ void connect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect_all(const string& parent_patch_path,
+ const string& node_path);
+
+ void set_port_value(const string& port_path,
+ const Raul::Atom& value);
+
+ void set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value);
+
+ void set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program);
+
+ void midi_learn(const string& node_path);
+
+ void set_variable(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ void set_property(const string& obj_path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ // Requests //
+ void ping();
+ void request_plugin(const string& uri);
+ void request_object(const string& path);
+ void request_port_value(const string& port_path);
+ void request_variable(const string& path, const string& key);
+ void request_property(const string& path, const string& key);
+ void request_plugins();
+ void request_all_objects();
+
+protected:
+ const string _engine_url;
+ int _client_port;
+ int32_t _id;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // OSCENGINESENDER_H
+
diff --git a/src/client/ObjectModel.cpp b/src/client/ObjectModel.cpp
new file mode 100644
index 00000000..ede5f822
--- /dev/null
+++ b/src/client/ObjectModel.cpp
@@ -0,0 +1,148 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <iostream>
+#include <raul/TableImpl.hpp>
+#include "interface/GraphObject.hpp"
+#include "ObjectModel.hpp"
+
+using namespace std;
+
+namespace Ingen {
+namespace Client {
+
+
+ObjectModel::ObjectModel(const Path& path)
+ : _path(path)
+{
+}
+
+
+ObjectModel::~ObjectModel()
+{
+}
+
+
+/** Get a variable for this object.
+ *
+ * @return Metadata value with key @a key, empty string otherwise.
+ */
+const Atom&
+ObjectModel::get_variable(const string& key) const
+{
+ static const Atom null_atom;
+
+ Variables::const_iterator i = _variables.find(key);
+ if (i != _variables.end())
+ return i->second;
+ else
+ return null_atom;
+}
+
+
+/** Get a variable for this object.
+ *
+ * @return Metadata value with key @a key, empty string otherwise.
+ */
+Atom&
+ObjectModel::get_variable( string& key)
+{
+ static Atom null_atom;
+
+ Variables::iterator i = _variables.find(key);
+ if (i != _variables.end())
+ return i->second;
+ else
+ return null_atom;
+}
+
+
+/** Get a property of this object.
+ *
+ * @return Metadata value with key @a key, empty string otherwise.
+ */
+const Atom&
+ObjectModel::get_property(const string& key) const
+{
+ static const Atom null_atom;
+
+ Properties::const_iterator i = _properties.find(key);
+ if (i != _properties.end())
+ return i->second;
+ else
+ return null_atom;
+}
+
+
+/** Get a property of this object.
+ *
+ * @return Metadata value with key @a key, empty string otherwise.
+ */
+Atom&
+ObjectModel::get_property(const string& key)
+{
+ static Atom null_atom;
+
+ Properties::iterator i = _properties.find(key);
+ if (i != _properties.end())
+ return i->second;
+ else
+ return null_atom;
+}
+
+
+bool
+ObjectModel::polyphonic() const
+{
+ Properties::const_iterator i = _properties.find("ingen:polyphonic");
+ return (i != _properties.end() && i->second.type() == Atom::BOOL && i->second.get_bool());
+}
+
+
+/** Merge the data of @a model with self, as much as possible.
+ *
+ * This will merge the two models, but with any conflict take the value in
+ * @a model as correct. The paths of the two models MUST be equal.
+ */
+void
+ObjectModel::set(SharedPtr<ObjectModel> o)
+{
+ assert(_path == o->path());
+ if (o->_parent)
+ _parent = o->_parent;
+
+ for (Variables::const_iterator v = o->variables().begin(); v != o->variables().end(); ++v) {
+ Variables::const_iterator mine = _variables.find(v->first);
+ if (mine != _variables.end())
+ cerr << "WARNING: " << _path << "Client/Server variable mismatch: " << v->first << endl;
+ _variables[v->first] = v->second;
+ signal_variable.emit(v->first, v->second);
+ }
+
+ for (Properties::const_iterator v = o->properties().begin(); v != o->properties().end(); ++v) {
+ Properties::const_iterator mine = _properties.find(v->first);
+ if (mine != _properties.end())
+ cerr << "WARNING: " << _path << "Client/Server property mismatch: " << v->first << endl;
+ _properties[v->first] = v->second;
+ signal_variable.emit(v->first, v->second);
+ }
+}
+
+
+} // namespace Client
+} // namespace Ingen
+
diff --git a/src/client/ObjectModel.hpp b/src/client/ObjectModel.hpp
new file mode 100644
index 00000000..11cc87a4
--- /dev/null
+++ b/src/client/ObjectModel.hpp
@@ -0,0 +1,115 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 OBJECTMODEL_H
+#define OBJECTMODEL_H
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <algorithm>
+#include <cassert>
+#include <boost/utility.hpp>
+#include <sigc++/sigc++.h>
+#include <raul/Atom.hpp>
+#include <raul/Path.hpp>
+#include <raul/SharedPtr.hpp>
+#include <raul/PathTable.hpp>
+#include "interface/GraphObject.hpp"
+
+using Raul::PathTable;
+using std::string;
+using Raul::Atom;
+using Raul::Path;
+using Raul::Symbol;
+
+namespace Ingen {
+namespace Client {
+
+class ClientStore;
+
+
+/** Base class for all GraphObject models (NodeModel, PatchModel, PortModel).
+ *
+ * There are no non-const public methods intentionally, models are not allowed
+ * to be manipulated directly by anything (but the Store) because of the
+ * asynchronous nature of engine control. To change something, use the
+ * controller (which the model probably shouldn't have a reference to but oh
+ * well, it reduces Collection Hell) and wait for the result (as a signal
+ * from this Model).
+ *
+ * \ingroup IngenClient
+ */
+class ObjectModel : virtual public Ingen::Shared::GraphObject
+{
+public:
+ virtual ~ObjectModel();
+
+ const Atom& get_variable(const string& key) const;
+ Atom& get_variable( string& key);
+ const Atom& get_property(const string& key) const;
+ Atom& get_property(const string& key);
+
+ virtual void set_variable(const string& key, const Atom& value)
+ { _variables[key] = value; signal_variable.emit(key, value); }
+
+ virtual void set_property(const string& key, const Atom& value)
+ { _properties[key] = value; signal_property.emit(key, value); }
+
+ const Variables& variables() const { return _variables; }
+ const Properties& properties() const { return _properties; }
+ Variables& variables() { return _variables; }
+ Properties& properties() { return _properties; }
+ const Path path() const { return _path; }
+ const Symbol symbol() const { return _path.name(); }
+ SharedPtr<ObjectModel> parent() const { return _parent; }
+ bool polyphonic() const;
+
+ GraphObject* graph_parent() const { return _parent.get(); }
+
+ // Signals
+ sigc::signal<void, SharedPtr<ObjectModel> > signal_new_child;
+ sigc::signal<void, SharedPtr<ObjectModel> > signal_removed_child;
+ sigc::signal<void, const string&, const Atom&> signal_variable;
+ sigc::signal<void, const string&, const Atom&> signal_property;
+ sigc::signal<void> signal_destroyed;
+ sigc::signal<void> signal_renamed;
+
+protected:
+ friend class ClientStore;
+
+ ObjectModel(const Path& path);
+
+ virtual void set_path(const Path& p) { _path = p; signal_renamed.emit(); }
+ virtual void set_parent(SharedPtr<ObjectModel> p) { assert(p); _parent = p; }
+ virtual void add_child(SharedPtr<ObjectModel> c) {}
+ virtual bool remove_child(SharedPtr<ObjectModel> c) { return true; }
+
+ virtual void set(SharedPtr<ObjectModel> model);
+
+ Path _path;
+ SharedPtr<ObjectModel> _parent;
+
+ Variables _variables;
+ Properties _properties;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // OBJECTMODEL_H
diff --git a/src/client/PatchModel.cpp b/src/client/PatchModel.cpp
new file mode 100644
index 00000000..af20c9f8
--- /dev/null
+++ b/src/client/PatchModel.cpp
@@ -0,0 +1,190 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 "PatchModel.hpp"
+#include "NodeModel.hpp"
+#include "ConnectionModel.hpp"
+#include "ClientStore.hpp"
+#include <cassert>
+#include <iostream>
+
+using std::cerr; using std::cout; using std::endl;
+
+namespace Ingen {
+namespace Client {
+
+
+void
+PatchModel::add_child(SharedPtr<ObjectModel> c)
+{
+ assert(c->parent().get() == this);
+
+ SharedPtr<PortModel> pm = PtrCast<PortModel>(c);
+ if (pm) {
+ add_port(pm);
+ return;
+ }
+
+ SharedPtr<NodeModel> nm = PtrCast<NodeModel>(c);
+ if (nm)
+ signal_new_node.emit(nm);
+}
+
+
+bool
+PatchModel::remove_child(SharedPtr<ObjectModel> o)
+{
+ assert(o->path().is_child_of(_path));
+ assert(o->parent().get() == this);
+
+ SharedPtr<PortModel> pm = PtrCast<PortModel>(o);
+ if (pm)
+ remove_port(pm);
+
+ // Remove any connections which referred to this object,
+ // since they can't possibly exist anymore
+ for (Connections::iterator j = _connections->begin(); j != _connections->end() ; ) {
+
+ Connections::iterator next = j;
+ ++next;
+
+ SharedPtr<ConnectionModel> cm = PtrCast<ConnectionModel>(*j);
+ assert(cm);
+
+ if (cm->src_port_path().parent() == o->path()
+ || cm->src_port_path() == o->path()
+ || cm->dst_port_path().parent() == o->path()
+ || cm->dst_port_path() == o->path()) {
+ signal_removed_connection.emit(cm);
+ _connections->erase(j); // cuts our reference
+ assert(!get_connection(cm->src_port_path(), cm->dst_port_path())); // no duplicates
+ }
+ j = next;
+ }
+
+ SharedPtr<NodeModel> nm = PtrCast<NodeModel>(o);
+ if (nm)
+ signal_removed_node.emit(nm);
+
+ return true;
+}
+
+
+void
+PatchModel::clear()
+{
+ _connections->clear();
+
+ NodeModel::clear();
+
+ assert(_connections->empty());
+ assert(_ports.empty());
+}
+
+
+SharedPtr<ConnectionModel>
+PatchModel::get_connection(const string& src_port_path, const string& dst_port_path) const
+{
+ for (Connections::const_iterator i = _connections->begin(); i != _connections->end(); ++i)
+ if ((*i)->src_port_path() == src_port_path && (*i)->dst_port_path() == dst_port_path)
+ return PtrCast<ConnectionModel>(*i);
+
+ return SharedPtr<ConnectionModel>();
+}
+
+
+/** Add a connection to this patch.
+ *
+ * A reference to @a cm is taken, released on deletion or removal.
+ * If @a cm only contains paths (not pointers to the actual ports), the ports
+ * will be found and set. The ports referred to not existing as children of
+ * this patch is a fatal error.
+ */
+void
+PatchModel::add_connection(SharedPtr<ConnectionModel> cm)
+{
+ // Store should have 'resolved' the connection already
+ assert(cm);
+ assert(cm->src_port());
+ assert(cm->dst_port());
+ assert(cm->src_port()->parent());
+ assert(cm->dst_port()->parent());
+ assert(cm->src_port_path() != cm->dst_port_path());
+ assert(cm->src_port()->parent().get() == this
+ || cm->src_port()->parent()->parent().get() == this);
+ assert(cm->dst_port()->parent().get() == this
+ || cm->dst_port()->parent()->parent().get() == this);
+
+ SharedPtr<ConnectionModel> existing = get_connection(cm->src_port_path(), cm->dst_port_path());
+
+ if (existing) {
+ assert(cm->src_port() == existing->src_port());
+ assert(cm->dst_port() == existing->dst_port());
+ } else {
+ _connections->push_back(new Connections::Node(cm));
+ signal_new_connection.emit(cm);
+ }
+}
+
+
+void
+PatchModel::remove_connection(const string& src_port_path, const string& dst_port_path)
+{
+ for (Connections::iterator i = _connections->begin(); i != _connections->end(); ++i) {
+ SharedPtr<ConnectionModel> cm = PtrCast<ConnectionModel>(*i);
+ assert(cm);
+ if (cm->src_port_path() == src_port_path && cm->dst_port_path() == dst_port_path) {
+ signal_removed_connection.emit(cm);
+ delete _connections->erase(i); // cuts our reference
+ assert(!get_connection(src_port_path, dst_port_path)); // no duplicates
+ return;
+ }
+ }
+
+ cerr << "[PatchModel::remove_connection] WARNING: Failed to find connection " <<
+ src_port_path << " -> " << dst_port_path << endl;
+}
+
+
+bool
+PatchModel::enabled() const
+{
+ Properties::const_iterator i = _properties.find("ingen:enabled");
+ return (i != _properties.end() && i->second.type() == Atom::BOOL && i->second.get_bool());
+}
+
+
+void
+PatchModel::set_property(const string& key, const Atom& value)
+{
+ ObjectModel::set_property(key, value);
+ if (key == "ingen:polyphony")
+ _poly = value.get_int32();
+}
+
+
+bool
+PatchModel::polyphonic() const
+{
+ return (_parent)
+ ? (_poly > 1) && _poly == PtrCast<PatchModel>(_parent)->poly() && _poly > 1
+ : (_poly > 1);
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/PatchModel.hpp b/src/client/PatchModel.hpp
new file mode 100644
index 00000000..70c8df0e
--- /dev/null
+++ b/src/client/PatchModel.hpp
@@ -0,0 +1,105 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 PATCHMODEL_H
+#define PATCHMODEL_H
+
+#include <cassert>
+#include <list>
+#include <string>
+#include <sigc++/sigc++.h>
+#include <raul/SharedPtr.hpp>
+#include "interface/Patch.hpp"
+#include "NodeModel.hpp"
+
+#include "ConnectionModel.hpp"
+
+using std::list; using std::string;
+
+namespace Ingen {
+namespace Client {
+
+class ClientStore;
+
+
+/** Client's model of a patch.
+ *
+ * \ingroup IngenClient
+ */
+class PatchModel : public NodeModel, public Ingen::Shared::Patch
+{
+public:
+ /* WARNING: Copy constructor creates a shallow copy WRT connections */
+
+ const Connections& connections() const { return *_connections.get(); }
+
+ SharedPtr<ConnectionModel> get_connection(const string& src_port_path,
+ const string& dst_port_path) const;
+
+ uint32_t poly() const { return _poly; }
+ uint32_t internal_polyphony() const { return _poly; }
+ bool enabled() const;
+ bool polyphonic() const;
+
+ /** "editable" = arranging,connecting,adding,deleting,etc
+ * not editable (control mode) you can just change controllers (performing)
+ */
+ bool get_editable() const { return _editable; }
+ void set_editable(bool e) { if (_editable != e) {
+ _editable = e;
+ signal_editable.emit(e);
+ } }
+
+ virtual void set_property(const string& key, const Atom& value);
+
+ // Signals
+ sigc::signal<void, SharedPtr<NodeModel> > signal_new_node;
+ sigc::signal<void, SharedPtr<NodeModel> > signal_removed_node;
+ sigc::signal<void, SharedPtr<ConnectionModel> > signal_new_connection;
+ sigc::signal<void, SharedPtr<ConnectionModel> > signal_removed_connection;
+ sigc::signal<void, bool> signal_editable;
+
+private:
+ friend class ClientStore;
+
+ PatchModel(const Path& patch_path, size_t internal_poly)
+ : NodeModel("ingen:Patch", patch_path)
+ , _connections(new Connections())
+ , _poly(internal_poly)
+ , _editable(true)
+ {
+ }
+
+ void clear();
+ void add_child(SharedPtr<ObjectModel> c);
+ bool remove_child(SharedPtr<ObjectModel> c);
+
+ void add_connection(SharedPtr<ConnectionModel> cm);
+ void remove_connection(const string& src_port_path, const string& dst_port_path);
+
+ SharedPtr<Connections> _connections;
+ uint32_t _poly;
+ bool _editable;
+};
+
+typedef Table<string, SharedPtr<PatchModel> > PatchModelMap;
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // PATCHMODEL_H
diff --git a/src/client/PluginModel.cpp b/src/client/PluginModel.cpp
new file mode 100644
index 00000000..ff7e5b5c
--- /dev/null
+++ b/src/client/PluginModel.cpp
@@ -0,0 +1,143 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <sstream>
+#include <raul/Path.hpp>
+#include "PluginModel.hpp"
+#include "PatchModel.hpp"
+#include "PluginUI.hpp"
+
+using namespace std;
+using Ingen::Shared::EngineInterface;
+
+namespace Ingen {
+namespace Client {
+
+#ifdef HAVE_SLV2
+SLV2World PluginModel::_slv2_world = NULL;
+SLV2Plugins PluginModel::_slv2_plugins = NULL;
+#endif
+
+Redland::World* PluginModel::_rdf_world = NULL;
+
+
+string
+PluginModel::default_node_symbol()
+{
+ return Raul::Path::nameify(_symbol);
+}
+
+
+string
+PluginModel::human_name()
+{
+#ifdef HAVE_SLV2
+ if (_slv2_plugin) {
+ SLV2Value name = slv2_plugin_get_name(_slv2_plugin);
+ string ret = slv2_value_as_string(name);
+ slv2_value_free(name);
+ return ret;
+ }
+#endif
+ return default_node_symbol();
+}
+
+
+string
+PluginModel::port_human_name(uint32_t index)
+{
+#ifdef HAVE_SLV2
+ if (_slv2_plugin) {
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+ SLV2Port port = slv2_plugin_get_port_by_index(_slv2_plugin, index);
+ SLV2Value name = slv2_port_get_name(_slv2_plugin, port);
+ string ret = slv2_value_as_string(name);
+ slv2_value_free(name);
+ return ret;
+ }
+#endif
+ return "";
+}
+
+
+#ifdef HAVE_SLV2
+bool
+PluginModel::has_ui() const
+{
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+
+ SLV2Value gtk_gui_uri = slv2_value_new_uri(_slv2_world,
+ "http://lv2plug.in/ns/extensions/ui#GtkUI");
+
+ SLV2UIs uis = slv2_plugin_get_uis(_slv2_plugin);
+
+ if (slv2_values_size(uis) > 0)
+ for (unsigned i=0; i < slv2_uis_size(uis); ++i)
+ if (slv2_ui_is_a(slv2_uis_get_at(uis, i), gtk_gui_uri))
+ return true;
+
+ return false;
+}
+
+
+SharedPtr<PluginUI>
+PluginModel::ui(Ingen::Shared::World* world, SharedPtr<NodeModel> node) const
+{
+ if (_type != LV2)
+ return SharedPtr<PluginUI>();
+
+ SharedPtr<PluginUI> ret = PluginUI::create(world, node, _slv2_plugin);
+ return ret;
+}
+
+
+const string&
+PluginModel::icon_path() const
+{
+ if (_icon_path == "" && _type == LV2) {
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+ _icon_path = get_lv2_icon_path(_slv2_plugin);
+ }
+
+ return _icon_path;
+}
+
+
+/** RDF world mutex must be held by the caller */
+string
+PluginModel::get_lv2_icon_path(SLV2Plugin plugin)
+{
+ string result;
+ SLV2Value svg_icon_pred = slv2_value_new_uri(_slv2_world,
+ "http://ll-plugins.nongnu.org/lv2/namespace#svgIcon");
+
+ SLV2Values paths = slv2_plugin_get_value(plugin, svg_icon_pred);
+
+ if (slv2_values_size(paths) > 0) {
+ SLV2Value value = slv2_values_get_at(paths, 0);
+ if (slv2_value_is_uri(value))
+ result = slv2_uri_to_path(slv2_value_as_string(value));
+ slv2_values_free(paths);
+ }
+
+ slv2_value_free(svg_icon_pred);
+ return result;
+}
+#endif
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/PluginModel.hpp b/src/client/PluginModel.hpp
new file mode 100644
index 00000000..e2137e19
--- /dev/null
+++ b/src/client/PluginModel.hpp
@@ -0,0 +1,142 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 PLUGINMODEL_H
+#define PLUGINMODEL_H
+
+#include CONFIG_H_PATH
+#include <string>
+#include <iostream>
+#include <raul/Path.hpp>
+#include <raul/SharedPtr.hpp>
+#include <redlandmm/World.hpp>
+#ifdef HAVE_SLV2
+#include <slv2/slv2.h>
+#endif
+#include "interface/EngineInterface.hpp"
+#include "interface/Plugin.hpp"
+#include "module/World.hpp"
+
+using std::string;
+
+namespace Ingen {
+namespace Client {
+
+class PatchModel;
+class NodeModel;
+class PluginUI;
+
+
+/** Model for a plugin available for loading.
+ *
+ * \ingroup IngenClient
+ */
+class PluginModel : public Ingen::Shared::Plugin
+{
+public:
+ PluginModel(const string& uri, const string& type_uri, const string& symbol, const string& name)
+ : _type(type_from_uri(type_uri))
+ , _uri(uri)
+ , _symbol(symbol)
+ , _name(name)
+ {
+#ifdef HAVE_SLV2
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+ SLV2Value plugin_uri = slv2_value_new_uri(_slv2_world, uri.c_str());
+ _slv2_plugin = slv2_plugins_get_by_uri(_slv2_plugins, plugin_uri);
+ slv2_value_free(plugin_uri);
+#endif
+ }
+
+ Type type() const { return _type; }
+ const string& uri() const { return _uri; }
+ const string& name() const { return _name; }
+
+ /** DEPRECATED */
+ Type type_from_string(const string& type_string) {
+ if (type_string == "LV2") return LV2;
+ else if (type_string == "LADSPA") return LADSPA;
+ else if (type_string == "Internal") return Internal;
+ else if (type_string == "Patch") return Patch;
+ else return Internal; // ?
+ }
+
+ Type type_from_uri(const string& type_uri) {
+ if (type_uri.substr(0, 6) != "ingen:") {
+ return Plugin::Internal; // ?
+ } else {
+ return type_from_string(type_uri.substr(6));
+ }
+ }
+
+ string default_node_symbol();
+ string human_name();
+ string port_human_name(uint32_t index);
+
+#ifdef HAVE_SLV2
+ static SLV2World slv2_world() { return _slv2_world; }
+ SLV2Plugin slv2_plugin() { return _slv2_plugin; }
+
+ SLV2Port slv2_port(uint32_t index) {
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+ return slv2_plugin_get_port_by_index(_slv2_plugin, index);
+ }
+
+ static void set_slv2_world(SLV2World world) {
+ Glib::Mutex::Lock lock(_rdf_world->mutex());
+ _slv2_world = world;
+ _slv2_plugins = slv2_world_get_all_plugins(_slv2_world);
+ }
+
+ bool has_ui() const;
+
+ SharedPtr<PluginUI> ui(Ingen::Shared::World* world,
+ SharedPtr<NodeModel> node) const;
+
+ const string& icon_path() const;
+ static string get_lv2_icon_path(SLV2Plugin plugin);
+#endif
+
+ static void set_rdf_world(Redland::World& world) {
+ _rdf_world = &world;
+ }
+
+ static Redland::World* rdf_world() { return _rdf_world; }
+
+private:
+ const Type _type;
+ const string _uri;
+ const string _symbol;
+ const string _name;
+
+#ifdef HAVE_SLV2
+ static SLV2World _slv2_world;
+ static SLV2Plugins _slv2_plugins;
+
+ SLV2Plugin _slv2_plugin;
+ mutable string _icon_path;
+#endif
+
+ static Redland::World* _rdf_world;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // PLUGINMODEL_H
+
diff --git a/src/client/PluginUI.cpp b/src/client/PluginUI.cpp
new file mode 100644
index 00000000..9c562135
--- /dev/null
+++ b/src/client/PluginUI.cpp
@@ -0,0 +1,157 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 <iostream>
+#include "lv2ext/lv2_event_helpers.h"
+#include "shared/LV2URIMap.hpp"
+#include "PluginUI.hpp"
+#include "NodeModel.hpp"
+#include "PortModel.hpp"
+
+using namespace std;
+using Ingen::Shared::EngineInterface;
+using Ingen::Shared::LV2URIMap;
+using Ingen::Shared::LV2Features;
+
+namespace Ingen {
+namespace Client {
+
+static void
+lv2_ui_write(LV2UI_Controller controller,
+ uint32_t port_index,
+ uint32_t buffer_size,
+ uint32_t format,
+ const void* buffer)
+{
+ /*
+ cerr << "lv2_ui_write (format " << format << "):" << endl;
+ fprintf(stderr, "RAW:\n");
+ for (uint32_t i=0; i < buffer_size; ++i) {
+ unsigned char byte = ((unsigned char*)buffer)[i];
+ if (byte >= 32 && byte <= 126)
+ fprintf(stderr, "%c ", ((unsigned char*)buffer)[i]);
+ else
+ fprintf(stderr, "%2X ", ((unsigned char*)buffer)[i]);
+ }
+ fprintf(stderr, "\n");
+ */
+
+ PluginUI* ui = (PluginUI*)controller;
+
+ SharedPtr<PortModel> port = ui->node()->ports()[port_index];
+
+ const LV2Features::Feature* f = ui->world()->lv2_features->feature(LV2_URI_MAP_URI);
+ LV2URIMap* map = (LV2URIMap*)f->controller;
+ assert(map);
+
+ // float (special case, always 0)
+ if (format == 0) {
+ assert(buffer_size == 4);
+ if (*(float*)buffer == port->value().get_float())
+ return; // do nothing (handle stupid plugin UIs that feed back)
+
+ ui->world()->engine->set_port_value(port->path(), Atom(*(float*)buffer));
+
+ // FIXME: slow, need to cache ID
+ } else if (format == map->uri_to_id(NULL, "http://lv2plug.in/ns/extensions/ui#Events")) {
+ uint32_t midi_event_type = map->uri_to_id(NULL, "http://lv2plug.in/ns/ext/midi#MidiEvent");
+ LV2_Event_Buffer* buf = (LV2_Event_Buffer*)buffer;
+ LV2_Event_Iterator iter;
+ uint8_t* data;
+ lv2_event_begin(&iter, buf);
+ while (lv2_event_is_valid(&iter)) {
+ LV2_Event* const ev = lv2_event_get(&iter, &data);
+ if (ev->type == midi_event_type) {
+ // FIXME: bundle multiple events by writing an entire buffer here
+ ui->world()->engine->set_port_value(port->path(),
+ Atom("lv2_midi:MidiEvent", ev->size, data));
+ } else {
+ cerr << "WARNING: Unable to send event type " << ev->type <<
+ " over OSC, ignoring event" << endl;
+ }
+
+ lv2_event_increment(&iter);
+ }
+ } else {
+ cerr << "WARNING: Unknown value format " << format
+ << ", either plugin " << ui->node()->plugin()->uri() << " is broken"
+ << " or this is an Ingen bug" << endl;
+ }
+}
+
+
+PluginUI::PluginUI(Ingen::Shared::World* world,
+ SharedPtr<NodeModel> node)
+ : _world(world)
+ , _node(node)
+ , _instance(NULL)
+{
+}
+
+
+PluginUI::~PluginUI()
+{
+ Glib::Mutex::Lock lock(PluginModel::rdf_world()->mutex());
+ slv2_ui_instance_free(_instance);
+}
+
+
+SharedPtr<PluginUI>
+PluginUI::create(Ingen::Shared::World* world,
+ SharedPtr<NodeModel> node,
+ SLV2Plugin plugin)
+{
+ Glib::Mutex::Lock lock(PluginModel::rdf_world()->mutex());
+ SharedPtr<PluginUI> ret;
+
+ SLV2Value gtk_gui_uri = slv2_value_new_uri(world->slv2_world,
+ "http://lv2plug.in/ns/extensions/ui#GtkUI");
+
+ SLV2UIs uis = slv2_plugin_get_uis(plugin);
+ SLV2UI ui = NULL;
+
+ if (slv2_values_size(uis) > 0) {
+ for (unsigned i=0; i < slv2_uis_size(uis); ++i) {
+ SLV2UI this_ui = slv2_uis_get_at(uis, i);
+ if (slv2_ui_is_a(this_ui, gtk_gui_uri)) {
+ ui = this_ui;
+ break;
+ }
+ }
+ }
+
+ if (ui) {
+ cout << "Found GTK Plugin UI: " << slv2_ui_get_uri(ui) << endl;
+ ret = SharedPtr<PluginUI>(new PluginUI(world, node));
+ SLV2UIInstance inst = slv2_ui_instantiate(
+ plugin, ui, lv2_ui_write, ret.get(), world->lv2_features->lv2_features());
+
+ if (inst) {
+ ret->set_instance(inst);
+ } else {
+ cerr << "ERROR: Failed to instantiate Plugin UI" << endl;
+ ret = SharedPtr<PluginUI>();
+ }
+ }
+
+ slv2_value_free(gtk_gui_uri);
+ return ret;
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/PluginUI.hpp b/src/client/PluginUI.hpp
new file mode 100644
index 00000000..d20dd16a
--- /dev/null
+++ b/src/client/PluginUI.hpp
@@ -0,0 +1,65 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 PLUGINUI_H
+#define PLUGINUI_H
+
+#include <slv2/slv2.h>
+#include <raul/SharedPtr.hpp>
+#include "module/World.hpp"
+
+namespace Ingen {
+namespace Shared { class EngineInterface; }
+namespace Client {
+
+class NodeModel;
+
+
+/** Model for a plugin available for loading.
+ *
+ * \ingroup IngenClient
+ */
+class PluginUI {
+public:
+ ~PluginUI();
+
+ static SharedPtr<PluginUI> create(Ingen::Shared::World* world,
+ SharedPtr<NodeModel> node,
+ SLV2Plugin plugin);
+
+ Ingen::Shared::World* world() const { return _world; }
+ SharedPtr<NodeModel> node() const { return _node; }
+ SLV2UIInstance instance() const { return _instance; }
+
+private:
+ PluginUI(Ingen::Shared::World* world,
+ SharedPtr<NodeModel> node);
+
+ void set_instance(SLV2UIInstance instance) { _instance = instance; }
+
+ Ingen::Shared::World* _world;
+ SharedPtr<NodeModel> _node;
+ SLV2UIInstance _instance;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // PLUGINUI_H
+
+
diff --git a/src/client/PortModel.cpp b/src/client/PortModel.cpp
new file mode 100644
index 00000000..c18378db
--- /dev/null
+++ b/src/client/PortModel.cpp
@@ -0,0 +1,66 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 "PortModel.hpp"
+#include "NodeModel.hpp"
+
+namespace Ingen {
+namespace Client {
+
+
+bool
+PortModel::is_logarithmic() const
+{
+ const Atom& hint = get_variable("ingen:logarithmic");
+ return (hint.is_valid() && hint.get_bool() > 0);
+}
+
+
+bool
+PortModel::is_integer() const
+{
+ const Atom& hint = get_variable("ingen:integer");
+ return (hint.is_valid() && hint.get_bool() > 0);
+}
+
+
+bool
+PortModel::is_toggle() const
+{
+ const Atom& hint = get_variable("ingen:toggled");
+ return (hint.is_valid() && hint.get_bool() > 0);
+}
+
+
+void
+PortModel::set(SharedPtr<ObjectModel> model)
+{
+ SharedPtr<PortModel> port = PtrCast<PortModel>(model);
+ if (port) {
+ _index = port->_index;
+ _type = port->_type;
+ _direction = port->_direction;
+ _current_val = port->_current_val;
+ _connections = port->_connections;
+ signal_value_changed.emit(_current_val);
+ }
+
+ ObjectModel::set(model);
+}
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/PortModel.hpp b/src/client/PortModel.hpp
new file mode 100644
index 00000000..a7f52679
--- /dev/null
+++ b/src/client/PortModel.hpp
@@ -0,0 +1,112 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 PORTMODEL_H
+#define PORTMODEL_H
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <sigc++/sigc++.h>
+#include <raul/SharedPtr.hpp>
+#include <raul/Path.hpp>
+#include "interface/Port.hpp"
+#include "ObjectModel.hpp"
+
+using std::string; using std::vector;
+
+namespace Ingen {
+namespace Client {
+
+
+/** Model of a port.
+ *
+ * \ingroup IngenClient
+ */
+class PortModel : public ObjectModel, public Ingen::Shared::Port
+{
+public:
+ enum Direction { INPUT, OUTPUT };
+
+ inline uint32_t index() const { return _index; }
+ inline DataType type() const { return _type; }
+ inline const Atom& value() const { return _current_val; }
+ inline bool connected() const { return (_connections > 0); }
+ inline bool is_input() const { return (_direction == INPUT); }
+ inline bool is_output() const { return (_direction == OUTPUT); }
+
+ bool is_logarithmic() const;
+ bool is_integer() const;
+ bool is_toggle() const;
+
+ inline bool operator==(const PortModel& pm) const { return (_path == pm._path); }
+
+ inline void value(const Atom& val) {
+ if (val != _current_val) {
+ _current_val = val;
+ signal_value_changed.emit(val);
+ }
+ }
+
+ inline void value(uint32_t voice, const Atom& val) {
+ // FIXME: implement properly
+ signal_voice_changed.emit(voice, val);
+ }
+
+ // Signals
+ sigc::signal<void, const Atom&> signal_value_changed; ///< Value ports
+ sigc::signal<void, uint32_t, const Atom&> signal_voice_changed; ///< Polyphonic value ports
+ sigc::signal<void> signal_activity; ///< Message ports
+ sigc::signal<void, SharedPtr<PortModel> > signal_connection;
+ sigc::signal<void, SharedPtr<PortModel> > signal_disconnection;
+
+private:
+ friend class ClientStore;
+
+ PortModel(const Path& path, uint32_t index, DataType type, Direction dir)
+ : ObjectModel(path)
+ , _index(index)
+ , _type(type)
+ , _direction(dir)
+ , _current_val(0.0f)
+ , _connections(0)
+ {
+ if (_type == DataType::UNKNOWN)
+ std::cerr << "[PortModel] Warning: Unknown port type" << std::endl;
+ }
+
+ void add_child(SharedPtr<ObjectModel> c) { throw; }
+ bool remove_child(SharedPtr<ObjectModel> c) { throw; }
+
+ void connected_to(SharedPtr<PortModel> p) { ++_connections; signal_connection.emit(p); }
+ void disconnected_from(SharedPtr<PortModel> p) { --_connections; signal_disconnection.emit(p); }
+
+ void set(SharedPtr<ObjectModel> model);
+
+ uint32_t _index;
+ DataType _type;
+ Direction _direction;
+ Atom _current_val;
+ size_t _connections;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // PORTMODEL_H
diff --git a/src/client/SigClientInterface.hpp b/src/client/SigClientInterface.hpp
new file mode 100644
index 00000000..7ab32c12
--- /dev/null
+++ b/src/client/SigClientInterface.hpp
@@ -0,0 +1,156 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 SIGCLIENTINTERFACE_H
+#define SIGCLIENTINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <sigc++/sigc++.h>
+#include "interface/ClientInterface.hpp"
+using std::string;
+
+namespace Ingen {
+namespace Client {
+
+
+/** A LibSigC++ signal emitting interface for clients to use.
+ *
+ * This simply emits an sigc signal for every event (eg OSC message) coming from
+ * the engine. Use Store (which extends this) if you want a nice client-side
+ * model of the engine.
+ *
+ * The signals here match the calls to ClientInterface exactly. See the
+ * documentation for ClientInterface for meanings of signal parameters.
+ */
+class SigClientInterface : public Ingen::Shared::ClientInterface, public sigc::trackable
+{
+public:
+ SigClientInterface() : _enabled(true) {}
+
+ bool enabled() const { return _enabled; }
+
+ std::string uri() const { return "(internal)"; }
+
+ // Signal parameters match up directly with ClientInterface calls
+
+ sigc::signal<void, int32_t> signal_response_ok;
+ sigc::signal<void, int32_t, string> signal_response_error;
+ sigc::signal<void> signal_bundle_begin;
+ sigc::signal<void> signal_bundle_end;
+ sigc::signal<void, string> signal_error;
+ sigc::signal<void, string, string, string, string> signal_new_plugin;
+ sigc::signal<void, string, uint32_t> signal_new_patch;
+ sigc::signal<void, string, string> signal_new_node;
+ sigc::signal<void, string, uint32_t, string, bool> signal_new_port;
+ sigc::signal<void, string> signal_patch_cleared;
+ sigc::signal<void, string, string> signal_object_renamed;
+ sigc::signal<void, string> signal_object_destroyed;
+ sigc::signal<void, string, string> signal_connection;
+ sigc::signal<void, string, string> signal_disconnection;
+ sigc::signal<void, string, string, Raul::Atom> signal_variable_change;
+ sigc::signal<void, string, string, Raul::Atom> signal_property_change;
+ sigc::signal<void, string, Raul::Atom> signal_port_value;
+ sigc::signal<void, string, uint32_t, Raul::Atom> signal_voice_value;
+ sigc::signal<void, string> signal_port_activity;
+ sigc::signal<void, string, uint32_t, uint32_t, string> signal_program_add;
+ sigc::signal<void, string, uint32_t, uint32_t> signal_program_remove;
+
+ /** Fire pending signals. Only does anything on derived classes (that may queue) */
+ virtual bool emit_signals() { return false; }
+
+protected:
+
+ bool _enabled;
+
+ // ClientInterface hooks that fire the above signals
+
+ void enable() { _enabled = true; }
+ void disable() { _enabled = false ; }
+
+ void bundle_begin()
+ { if (_enabled) signal_bundle_begin.emit(); }
+
+ void bundle_end()
+ { if (_enabled) signal_bundle_end.emit(); }
+
+ void transfer_begin() {}
+ void transfer_end() {}
+
+ void response_ok(int32_t id)
+ { if (_enabled) signal_response_ok.emit(id); }
+
+ void response_error(int32_t id, const string& msg)
+ { if (_enabled) signal_response_error.emit(id, msg); }
+
+ void error(const string& msg)
+ { if (_enabled) signal_error.emit(msg); }
+
+ void new_plugin(const string& uri, const string& type_uri, const string& symbol, const string& name)
+ { if (_enabled) signal_new_plugin.emit(uri, type_uri, symbol, name); }
+
+ void new_patch(const string& path, uint32_t poly)
+ { if (_enabled) signal_new_patch.emit(path, poly); }
+
+ void new_node(const string& path, const string& plugin_uri)
+ { if (_enabled) signal_new_node.emit(path, plugin_uri); }
+
+ void new_port(const string& path, uint32_t index, const string& data_type, bool is_output)
+ { if (_enabled) signal_new_port.emit(path, index, data_type, is_output); }
+
+ void connect(const string& src_port_path, const string& dst_port_path)
+ { if (_enabled) signal_connection.emit(src_port_path, dst_port_path); }
+
+ void destroy(const string& path)
+ { if (_enabled) signal_object_destroyed.emit(path); }
+
+ void patch_cleared(const string& path)
+ { if (_enabled) signal_patch_cleared.emit(path); }
+
+ void object_renamed(const string& old_path, const string& new_path)
+ { if (_enabled) signal_object_renamed.emit(old_path, new_path); }
+
+ void disconnect(const string& src_port_path, const string& dst_port_path)
+ { if (_enabled) signal_disconnection.emit(src_port_path, dst_port_path); }
+
+ void set_variable(const string& path, const string& key, const Raul::Atom& value)
+ { if (_enabled) signal_variable_change.emit(path, key, value); }
+
+ void set_property(const string& path, const string& key, const Raul::Atom& value)
+ { if (_enabled) signal_property_change.emit(path, key, value); }
+
+ void set_port_value(const string& port_path, const Raul::Atom& value)
+ { if (_enabled) signal_port_value.emit(port_path, value); }
+
+ void set_voice_value(const string& port_path, uint32_t voice, const Raul::Atom& value)
+ { if (_enabled) signal_voice_value.emit(port_path, voice, value); }
+
+ void port_activity(const string& port_path)
+ { if (_enabled) signal_port_activity.emit(port_path); }
+
+ void program_add(const string& path, uint32_t bank, uint32_t program, const string& name)
+ { if (_enabled) signal_program_add.emit(path, bank, program, name); }
+
+ void program_remove(const string& path, uint32_t bank, uint32_t program)
+ { if (_enabled) signal_program_remove.emit(path, bank, program); }
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif
diff --git a/src/client/ThreadedSigClientInterface.cpp b/src/client/ThreadedSigClientInterface.cpp
new file mode 100644
index 00000000..ef95133b
--- /dev/null
+++ b/src/client/ThreadedSigClientInterface.cpp
@@ -0,0 +1,78 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 "ThreadedSigClientInterface.hpp"
+#include <iostream>
+
+using namespace std;
+
+namespace Ingen {
+namespace Client {
+
+
+/** Push an event (from the engine, ie 'new patch') on to the queue.
+ */
+void
+ThreadedSigClientInterface::push_sig(Closure ev)
+{
+ _attached = true;
+ if (!_enabled)
+ return;
+
+ bool success = false;
+ while (!success) {
+ success = _sigs.push(ev);
+ if (!success) {
+ cerr << "WARNING: Client event queue full. Waiting..." << endl;
+ _mutex.lock();
+ _cond.wait(_mutex);
+ _mutex.unlock();
+ cerr << "Queue drained, continuing" << endl;
+ }
+ }
+}
+
+
+/** Process all queued events that came from the OSC thread.
+ *
+ * This function should be called from the Gtk thread to emit signals and cause
+ * the connected methods to execute.
+ */
+bool
+ThreadedSigClientInterface::emit_signals()
+{
+ // Process a limited number of events, to prevent locking the GTK
+ // thread indefinitely while processing continually arriving events
+
+ size_t num_processed = 0;
+ while (!_sigs.empty() && num_processed++ < (_sigs.capacity() * 3 / 4)) {
+ Closure& ev = _sigs.front();
+ ev();
+ ev.disconnect();
+ _sigs.pop();
+ }
+
+ _mutex.lock();
+ _cond.broadcast();
+ _mutex.unlock();
+
+ return true;
+}
+
+
+} // namespace Client
+} // namespace Ingen
diff --git a/src/client/ThreadedSigClientInterface.hpp b/src/client/ThreadedSigClientInterface.hpp
new file mode 100644
index 00000000..3014c139
--- /dev/null
+++ b/src/client/ThreadedSigClientInterface.hpp
@@ -0,0 +1,184 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 THREADEDSIGCLIENTINTERFACE_H
+#define THREADEDSIGCLIENTINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <sigc++/sigc++.h>
+#include <glibmm/thread.h>
+#include "interface/ClientInterface.hpp"
+#include "SigClientInterface.hpp"
+#include <raul/Atom.hpp>
+#include <raul/SRSWQueue.hpp>
+
+using std::string;
+
+/** Returns nothing and takes no parameters (because they have all been bound) */
+typedef sigc::slot<void> Closure;
+
+namespace Ingen {
+namespace Shared { class EngineInterface; }
+namespace Client {
+
+
+/** A LibSigC++ signal emitting interface for clients to use.
+ *
+ * This emits signals (possibly) in a different thread than the ClientInterface
+ * functions are called. It must be explicitly driven with the emit_signals()
+ * function, which fires all enqueued signals up until the present. You can
+ * use this in a GTK idle callback for receiving thread safe engine signals.
+ */
+class ThreadedSigClientInterface : public SigClientInterface
+{
+public:
+ ThreadedSigClientInterface(uint32_t queue_size)
+ : _sigs(queue_size)
+ , response_ok_slot(signal_response_ok.make_slot())
+ , response_error_slot(signal_response_error.make_slot())
+ , error_slot(signal_error.make_slot())
+ , new_plugin_slot(signal_new_plugin.make_slot())
+ , new_patch_slot(signal_new_patch.make_slot())
+ , new_node_slot(signal_new_node.make_slot())
+ , new_port_slot(signal_new_port.make_slot())
+ , connection_slot(signal_connection.make_slot())
+ , patch_cleared_slot(signal_patch_cleared.make_slot())
+ , object_destroyed_slot(signal_object_destroyed.make_slot())
+ , object_renamed_slot(signal_object_renamed.make_slot())
+ , disconnection_slot(signal_disconnection.make_slot())
+ , variable_change_slot(signal_variable_change.make_slot())
+ , property_change_slot(signal_property_change.make_slot())
+ , port_value_slot(signal_port_value.make_slot())
+ , port_activity_slot(signal_port_activity.make_slot())
+ , program_add_slot(signal_program_add.make_slot())
+ , program_remove_slot(signal_program_remove.make_slot())
+ {
+ }
+
+ virtual std::string uri() const { return "(internal)"; }
+
+ virtual void subscribe(Shared::EngineInterface* engine) { throw; }
+
+ bool enabled() const { return _attached; }
+
+ void bundle_begin()
+ { push_sig(bundle_begin_slot); }
+
+ void bundle_end()
+ { push_sig(bundle_end_slot); }
+
+ void transfer_begin() {}
+ void transfer_end() {}
+
+ void response_ok(int32_t id)
+ { push_sig(sigc::bind(response_ok_slot, id)); }
+
+ void response_error(int32_t id, const string& msg)
+ { push_sig(sigc::bind(response_error_slot, id, msg)); }
+
+ void error(const string& msg)
+ { push_sig(sigc::bind(error_slot, msg)); }
+
+ void new_plugin(const string& uri, const string& type_uri, const string& symbol, const string& name)
+ { push_sig(sigc::bind(new_plugin_slot, uri, type_uri, symbol, name)); }
+
+ void new_patch(const string& path, uint32_t poly)
+ { push_sig(sigc::bind(new_patch_slot, path, poly)); }
+
+ void new_node(const string& path, const string& plugin_uri)
+ { push_sig(sigc::bind(new_node_slot, path, plugin_uri)); }
+
+ void new_port(const string& path, uint32_t index, const string& data_type, bool is_output)
+ { push_sig(sigc::bind(new_port_slot, path, index, data_type, is_output)); }
+
+ void connect(const string& src_port_path, const string& dst_port_path)
+ { push_sig(sigc::bind(connection_slot, src_port_path, dst_port_path)); }
+
+ void destroy(const string& path)
+ { push_sig(sigc::bind(object_destroyed_slot, path)); }
+
+ void patch_cleared(const string& path)
+ { push_sig(sigc::bind(patch_cleared_slot, path)); }
+
+ void object_renamed(const string& old_path, const string& new_path)
+ { push_sig(sigc::bind(object_renamed_slot, old_path, new_path)); }
+
+ void disconnect(const string& src_port_path, const string& dst_port_path)
+ { push_sig(sigc::bind(disconnection_slot, src_port_path, dst_port_path)); }
+
+ void set_variable(const string& path, const string& key, const Raul::Atom& value)
+ { push_sig(sigc::bind(variable_change_slot, path, key, value)); }
+
+ void set_property(const string& path, const string& key, const Raul::Atom& value)
+ { push_sig(sigc::bind(property_change_slot, path, key, value)); }
+
+ void set_port_value(const string& port_path, const Raul::Atom& value)
+ { push_sig(sigc::bind(port_value_slot, port_path, value)); }
+
+ void set_voice_value(const string& port_path, uint32_t voice, const Raul::Atom& value)
+ { push_sig(sigc::bind(voice_value_slot, port_path, voice, value)); }
+
+ void port_activity(const string& port_path)
+ { push_sig(sigc::bind(port_activity_slot, port_path)); }
+
+ void program_add(const string& path, uint32_t bank, uint32_t program, const string& name)
+ { push_sig(sigc::bind(program_add_slot, path, bank, program, name)); }
+
+ void program_remove(const string& path, uint32_t bank, uint32_t program)
+ { push_sig(sigc::bind(program_remove_slot, path, bank, program)); }
+
+ /** Process all queued events - Called from GTK thread to emit signals. */
+ bool emit_signals();
+
+private:
+ void push_sig(Closure ev);
+
+ Glib::Mutex _mutex;
+ Glib::Cond _cond;
+
+ Raul::SRSWQueue<Closure> _sigs;
+ bool _attached;
+
+ sigc::slot<void> bundle_begin_slot;
+ sigc::slot<void> bundle_end_slot;
+ sigc::slot<void, int32_t> response_ok_slot;
+ sigc::slot<void, int32_t, string> response_error_slot;
+ sigc::slot<void, string> error_slot;
+ sigc::slot<void, string, string, string, string> new_plugin_slot;
+ sigc::slot<void, string, uint32_t> new_patch_slot;
+ sigc::slot<void, string, string> new_node_slot;
+ sigc::slot<void, string, uint32_t, string, bool> new_port_slot;
+ sigc::slot<void, string, string> connection_slot;
+ sigc::slot<void, string> patch_cleared_slot;
+ sigc::slot<void, string> object_destroyed_slot;
+ sigc::slot<void, string, string> object_renamed_slot;
+ sigc::slot<void, string, string> disconnection_slot;
+ sigc::slot<void, string, string, Raul::Atom> variable_change_slot;
+ sigc::slot<void, string, string, Raul::Atom> property_change_slot;
+ sigc::slot<void, string, Raul::Atom> port_value_slot;
+ sigc::slot<void, string, uint32_t, Raul::Atom> voice_value_slot;
+ sigc::slot<void, string> port_activity_slot;
+ sigc::slot<void, string, uint32_t, uint32_t, string> program_add_slot;
+ sigc::slot<void, string, uint32_t, uint32_t> program_remove_slot;
+};
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif
diff --git a/src/client/client.cpp b/src/client/client.cpp
new file mode 100644
index 00000000..f3d62471
--- /dev/null
+++ b/src/client/client.cpp
@@ -0,0 +1,57 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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 CONFIG_H_PATH
+
+#include <iostream>
+#include "client.hpp"
+#include "OSCEngineSender.hpp"
+#ifdef WITH_SOUP
+#include "HTTPEngineSender.hpp"
+#endif
+
+using namespace std;
+
+namespace Ingen {
+namespace Client {
+
+
+SharedPtr<Ingen::Shared::EngineInterface>
+new_remote_interface(const std::string& url)
+{
+ const string scheme = url.substr(0, url.find(":"));
+ cout << "SCHEME: " << scheme << endl;
+ if (scheme == "osc.udp" || scheme == "osc.tcp") {
+ OSCEngineSender* oes = new OSCEngineSender(url);
+ oes->attach(rand(), true);
+ return SharedPtr<Shared::EngineInterface>(oes);
+#ifdef WITH_SOUP
+ } else if (scheme == "http") {
+ HTTPEngineSender* hes = new HTTPEngineSender(url);
+ hes->attach(rand(), true);
+ return SharedPtr<Shared::EngineInterface>(hes);
+#endif
+ } else {
+ cerr << "WARNING: Unknown URI scheme '" << scheme << "'" << endl;
+ return SharedPtr<Shared::EngineInterface>();
+ }
+}
+
+
+} // namespace Client
+} // namespace Ingen
+
diff --git a/src/client/client.hpp b/src/client/client.hpp
new file mode 100644
index 00000000..82166da5
--- /dev/null
+++ b/src/client/client.hpp
@@ -0,0 +1,43 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave 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_CLIENT_H
+#define INGEN_CLIENT_H
+
+#include <raul/SharedPtr.hpp>
+
+namespace Ingen {
+
+class Engine;
+
+namespace Shared { class EngineInterface; }
+
+namespace Client {
+
+extern "C" {
+
+ SharedPtr<Shared::EngineInterface> new_remote_interface(const std::string& url);
+ SharedPtr<Shared::EngineInterface> new_queued_interface(SharedPtr<Ingen::Engine> engine);
+
+}
+
+
+} // namespace Client
+} // namespace Ingen
+
+#endif // INGEN_CLIENT_H
+
diff --git a/src/client/wscript b/src/client/wscript
new file mode 100644
index 00000000..6b4408a7
--- /dev/null
+++ b/src/client/wscript
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+import Params
+
+def build(bld):
+ obj = bld.create_obj('cpp', 'shlib')
+ obj.source = '''
+ ClientStore.cpp
+ NodeModel.cpp
+ ObjectModel.cpp
+ PatchModel.cpp
+ PluginModel.cpp
+ PluginUI.cpp
+ PortModel.cpp
+ ThreadedSigClientInterface.cpp
+ client.cpp
+ '''
+
+ if bld.env()['HAVE_SOUP']:
+ obj.source += '''
+ HTTPClientReceiver.cpp
+ HTTPEngineSender.cpp
+ '''
+
+ if bld.env()['HAVE_XML2']:
+ obj.source += ' DeprecatedLoader.cpp '
+
+ if bld.env()['HAVE_LIBLO']:
+ obj.source += ' OSCClientReceiver.cpp OSCEngineSender.cpp '
+
+ obj.includes = ['..', '../../common', '../..']
+ obj.name = 'libingen_client'
+ obj.target = 'ingen_client'
+ obj.uselib = 'GLIBMM SLV2 RAUL REDLANDMM SOUP XML2 SIGCPP'
+ obj.vnum = '0.0.0'
+