diff options
Diffstat (limited to 'src/client')
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' + |