diff options
Diffstat (limited to 'src/client')
-rw-r--r-- | src/client/BlockModel.cpp | 285 | ||||
-rw-r--r-- | src/client/ClientStore.cpp | 487 | ||||
-rw-r--r-- | src/client/GraphModel.cpp | 176 | ||||
-rw-r--r-- | src/client/ObjectModel.cpp | 108 | ||||
-rw-r--r-- | src/client/PluginModel.cpp | 360 | ||||
-rw-r--r-- | src/client/PluginUI.cpp | 336 | ||||
-rw-r--r-- | src/client/PortModel.cpp | 78 | ||||
-rw-r--r-- | src/client/ingen_client.cpp | 34 | ||||
-rw-r--r-- | src/client/wscript | 23 |
9 files changed, 1887 insertions, 0 deletions
diff --git a/src/client/BlockModel.cpp b/src/client/BlockModel.cpp new file mode 100644 index 00000000..910f7037 --- /dev/null +++ b/src/client/BlockModel.cpp @@ -0,0 +1,285 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cassert> +#include <cmath> +#include <string> + +#include "ingen/client/BlockModel.hpp" +#include "ingen/URIs.hpp" +#include "ingen/World.hpp" + +namespace Ingen { +namespace Client { + +BlockModel::BlockModel(URIs& uris, + SPtr<PluginModel> plugin, + const Raul::Path& path) + : ObjectModel(uris, path) + , _plugin_uri(plugin->uri()) + , _plugin(plugin) + , _num_values(0) + , _min_values(nullptr) + , _max_values(nullptr) +{ +} + +BlockModel::BlockModel(URIs& uris, + const URI& plugin_uri, + const Raul::Path& path) + : ObjectModel(uris, path) + , _plugin_uri(plugin_uri) + , _num_values(0) + , _min_values(nullptr) + , _max_values(nullptr) +{ +} + +BlockModel::BlockModel(const BlockModel& 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); +} + +BlockModel::~BlockModel() +{ + clear(); +} + +void +BlockModel::remove_port(SPtr<PortModel> port) +{ + for (auto i = _ports.begin(); i != _ports.end(); ++i) { + if ((*i) == port) { + _ports.erase(i); + break; + } + } + _signal_removed_port.emit(port); +} + +void +BlockModel::remove_port(const Raul::Path& port_path) +{ + for (auto i = _ports.begin(); i != _ports.end(); ++i) { + if ((*i)->path() == port_path) { + _ports.erase(i); + break; + } + } +} + +void +BlockModel::clear() +{ + _ports.clear(); + assert(_ports.empty()); + delete[] _min_values; + delete[] _max_values; + _min_values = nullptr; + _max_values = nullptr; +} + +void +BlockModel::add_child(SPtr<ObjectModel> c) +{ + assert(c->parent().get() == this); + + //ObjectModel::add_child(c); + + SPtr<PortModel> pm = dynamic_ptr_cast<PortModel>(c); + assert(pm); + add_port(pm); +} + +bool +BlockModel::remove_child(SPtr<ObjectModel> c) +{ + assert(c->path().is_child_of(path())); + assert(c->parent().get() == this); + + //bool ret = ObjectModel::remove_child(c); + + SPtr<PortModel> pm = dynamic_ptr_cast<PortModel>(c); + assert(pm); + remove_port(pm); + + //return ret; + return true; +} + +void +BlockModel::add_port(SPtr<PortModel> pm) +{ + assert(pm); + assert(pm->path().is_child_of(path())); + assert(pm->parent().get() == this); + + // Store should have handled this by merging the two + assert(find(_ports.begin(), _ports.end(), pm) == _ports.end()); + + _ports.push_back(pm); + _signal_new_port.emit(pm); +} + +SPtr<const PortModel> +BlockModel::get_port(const Raul::Symbol& symbol) const +{ + for (auto p : _ports) { + if (p->symbol() == symbol) { + return p; + } + } + return SPtr<PortModel>(); +} + +SPtr<const PortModel> +BlockModel::get_port(uint32_t index) const +{ + return _ports[index]; +} + +Ingen::Node* +BlockModel::port(uint32_t index) const +{ + assert(index < num_ports()); + return const_cast<Ingen::Node*>( + dynamic_cast<const Ingen::Node*>(_ports[index].get())); +} + +void +BlockModel::default_port_value_range(SPtr<const PortModel> port, + float& min, + float& max, + uint32_t srate) const +{ + // Default control values + min = 0.0; + max = 1.0; + + // Get range from client-side LV2 data + if (_plugin && _plugin->lilv_plugin()) { + if (!_min_values) { + _num_values = lilv_plugin_get_num_ports(_plugin->lilv_plugin()); + _min_values = new float[_num_values]; + _max_values = new float[_num_values]; + lilv_plugin_get_port_ranges_float(_plugin->lilv_plugin(), + _min_values, _max_values, nullptr); + } + + if (!std::isnan(_min_values[port->index()])) { + min = _min_values[port->index()]; + } + if (!std::isnan(_max_values[port->index()])) { + max = _max_values[port->index()]; + } + } + + if (port->port_property(_uris.lv2_sampleRate)) { + min *= srate; + max *= srate; + } +} + +void +BlockModel::port_value_range(SPtr<const PortModel> port, + float& min, + float& max, + uint32_t srate) const +{ + assert(port->parent().get() == this); + + default_port_value_range(port, min, max); + + // Possibly overriden + const Atom& min_atom = port->get_property(_uris.lv2_minimum); + const Atom& max_atom = port->get_property(_uris.lv2_maximum); + if (min_atom.type() == _uris.forge.Float) { + min = min_atom.get<float>(); + } + if (max_atom.type() == _uris.forge.Float) { + max = max_atom.get<float>(); + } + + if (max <= min) { + max = min + 1.0; + } + + if (port->port_property(_uris.lv2_sampleRate)) { + min *= srate; + max *= srate; + } +} + +std::string +BlockModel::label() const +{ + const Atom& name_property = get_property(_uris.lv2_name); + if (name_property.type() == _uris.forge.String) { + return name_property.ptr<char>(); + } else if (plugin_model()) { + return plugin_model()->human_name(); + } else { + return symbol().c_str(); + } +} + +std::string +BlockModel::port_label(SPtr<const PortModel> port) const +{ + const Atom& name = port->get_property(URI(LV2_CORE__name)); + if (name.is_valid() && name.type() == _uris.forge.String) { + return name.ptr<char>(); + } + + if (_plugin && _plugin->lilv_plugin()) { + LilvWorld* w = _plugin->lilv_world(); + const LilvPlugin* plug = _plugin->lilv_plugin(); + LilvNode* sym = lilv_new_string(w, port->symbol().c_str()); + const LilvPort* lport = lilv_plugin_get_port_by_symbol(plug, sym); + if (lport) { + LilvNode* lname = lilv_port_get_name(plug, lport); + if (lname && lilv_node_is_string(lname)) { + std::string ret(lilv_node_as_string(lname)); + lilv_node_free(lname); + return ret; + } + lilv_node_free(lname); + } + } + + return port->symbol().c_str(); +} + +void +BlockModel::set(SPtr<ObjectModel> model) +{ + SPtr<BlockModel> block = dynamic_ptr_cast<BlockModel>(model); + if (block) { + _plugin_uri = block->_plugin_uri; + _plugin = block->_plugin; + } + + ObjectModel::set(model); +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp new file mode 100644 index 00000000..792f8949 --- /dev/null +++ b/src/client/ClientStore.cpp @@ -0,0 +1,487 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <boost/variant/apply_visitor.hpp> + +#include "ingen/Log.hpp" +#include "ingen/client/ArcModel.hpp" +#include "ingen/client/BlockModel.hpp" +#include "ingen/client/ClientStore.hpp" +#include "ingen/client/GraphModel.hpp" +#include "ingen/client/ObjectModel.hpp" +#include "ingen/client/PluginModel.hpp" +#include "ingen/client/PortModel.hpp" +#include "ingen/client/SigClientInterface.hpp" + +namespace Ingen { +namespace Client { + +ClientStore::ClientStore(URIs& uris, + Log& log, + SPtr<SigClientInterface> emitter) + : _uris(uris) + , _log(log) + , _emitter(emitter) + , _plugins(new Plugins()) +{ + if (emitter) { + emitter->signal_message().connect( + sigc::mem_fun(this, &ClientStore::message)); + } +} + +void +ClientStore::clear() +{ + Store::clear(); + _plugins->clear(); +} + +void +ClientStore::add_object(SPtr<ObjectModel> object) +{ + // If we already have "this" object, merge the existing one into the new + // one (with precedence to the new values). + auto existing = find(object->path()); + if (existing != end()) { + dynamic_ptr_cast<ObjectModel>(existing->second)->set(object); + } else { + if (!object->path().is_root()) { + SPtr<ObjectModel> parent = _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); + } else { + _log.error(fmt("Object %1% with no parent\n") % object->path()); + } + } else { + (*this)[object->path()] = object; + _signal_new_object.emit(object); + } + } + + for (auto p : object->properties()) { + object->signal_property().emit(p.first, p.second); + } +} + +SPtr<ObjectModel> +ClientStore::remove_object(const Raul::Path& path) +{ + // Find the object, the "top" of the tree to remove + const iterator top = find(path); + if (top == end()) { + return SPtr<ObjectModel>(); + } + + SPtr<ObjectModel> object = dynamic_ptr_cast<ObjectModel>(top->second); + + // Remove object and any adjacent arcs from parent if applicable + if (object && object->parent()) { + SPtr<PortModel> port = dynamic_ptr_cast<PortModel>(object); + if (port && dynamic_ptr_cast<GraphModel>(port->parent())) { + disconnect_all(port->parent()->path(), path); + if (port->parent()->parent()) { + disconnect_all(port->parent()->parent()->path(), path); + } + } else if (port && port->parent()->parent()) { + disconnect_all(port->parent()->parent()->path(), path); + } else { + disconnect_all(object->parent()->path(), path); + } + + object->parent()->remove_child(object); + } + + // Remove the object and all its descendants + Objects removed; + remove(top, removed); + + // Notify everything that needs to know this object has been removed + if (object) { + object->signal_destroyed().emit(); + } + + return object; +} + +SPtr<PluginModel> +ClientStore::_plugin(const URI& uri) +{ + const Plugins::iterator i = _plugins->find(uri); + return (i == _plugins->end()) ? SPtr<PluginModel>() : (*i).second; +} + +SPtr<PluginModel> +ClientStore::_plugin(const Atom& uri) +{ + /* FIXME: Should probably be stored with URIs rather than strings, to make + this a fast case. */ + + const Plugins::iterator i = _plugins->find(URI(_uris.forge.str(uri, false))); + return (i == _plugins->end()) ? SPtr<PluginModel>() : (*i).second; +} + +SPtr<const PluginModel> +ClientStore::plugin(const URI& uri) const +{ + return const_cast<ClientStore*>(this)->_plugin(uri); +} + +SPtr<ObjectModel> +ClientStore::_object(const Raul::Path& path) +{ + const iterator i = find(path); + if (i == end()) { + return SPtr<ObjectModel>(); + } else { + SPtr<ObjectModel> model = dynamic_ptr_cast<ObjectModel>(i->second); + assert(model); + assert(model->path().is_root() || model->parent()); + return model; + } +} + +SPtr<const ObjectModel> +ClientStore::object(const Raul::Path& path) const +{ + return const_cast<ClientStore*>(this)->_object(path); +} + +SPtr<Resource> +ClientStore::_resource(const URI& uri) +{ + if (uri_is_path(uri)) { + return _object(uri_to_path(uri)); + } else { + return _plugin(uri); + } +} + +SPtr<const Resource> +ClientStore::resource(const URI& uri) const +{ + return const_cast<ClientStore*>(this)->_resource(uri); +} + +void +ClientStore::add_plugin(SPtr<PluginModel> pm) +{ + SPtr<PluginModel> existing = _plugin(pm->uri()); + if (existing) { + existing->set(pm); + } else { + _plugins->emplace(pm->uri(), pm); + _signal_new_plugin.emit(pm); + } +} + +/* ****** Signal Handlers ******** */ + +void +ClientStore::operator()(const Del& del) +{ + if (uri_is_path(del.uri)) { + remove_object(uri_to_path(del.uri)); + } else { + auto p = _plugins->find(del.uri); + if (p != _plugins->end()) { + _plugins->erase(p); + _signal_plugin_deleted.emit(del.uri); + } + } +} + +void +ClientStore::operator()(const Copy&) +{ + _log.error("Client store copy unsupported\n"); +} + +void +ClientStore::operator()(const Move& msg) +{ + const iterator top = find(msg.old_path); + if (top != end()) { + rename(top, msg.new_path); + } +} + +void +ClientStore::message(const Message& msg) +{ + boost::apply_visitor(*this, msg); +} + +void +ClientStore::operator()(const Put& msg) +{ + typedef Properties::const_iterator Iterator; + + const auto& uri = msg.uri; + const auto& properties = msg.properties; + + bool is_graph, is_block, is_port, is_output; + Resource::type(uris(), properties, + is_graph, is_block, is_port, is_output); + + // Check for specially handled types + const Iterator t = properties.find(_uris.rdf_type); + if (t != properties.end()) { + const Atom& type(t->second); + if (_uris.pset_Preset == type) { + const Iterator p = properties.find(_uris.lv2_appliesTo); + const Iterator l = properties.find(_uris.rdfs_label); + SPtr<PluginModel> plug; + if (p == properties.end()) { + _log.error(fmt("Preset <%1%> with no plugin\n") % uri.c_str()); + } else if (l == properties.end()) { + _log.error(fmt("Preset <%1%> with no label\n") % uri.c_str()); + } else if (l->second.type() != _uris.forge.String) { + _log.error(fmt("Preset <%1%> label is not a string\n") % uri.c_str()); + } else if (!(plug = _plugin(p->second))) { + _log.error(fmt("Preset <%1%> for unknown plugin %2%\n") + % uri.c_str() % _uris.forge.str(p->second, true)); + } else { + plug->add_preset(uri, l->second.ptr<char>()); + } + return; + } else if (_uris.ingen_Graph == type) { + is_graph = true; + } else if (_uris.ingen_Internal == type || _uris.lv2_Plugin == type) { + SPtr<PluginModel> p(new PluginModel(uris(), uri, type, properties)); + add_plugin(p); + return; + } + } + + if (!uri_is_path(uri)) { + _log.error(fmt("Put for unknown subject <%1%>\n") + % uri.c_str()); + return; + } + + const Raul::Path path(uri_to_path(uri)); + + SPtr<ObjectModel> obj = dynamic_ptr_cast<ObjectModel>(_object(path)); + if (obj) { + obj->set_properties(properties); + return; + } + + if (path.is_root()) { + is_graph = true; + } + + if (is_graph) { + SPtr<GraphModel> model(new GraphModel(uris(), path)); + model->set_properties(properties); + add_object(model); + } else if (is_block) { + auto p = properties.find(_uris.lv2_prototype); + if (p == properties.end()) { + p = properties.find(_uris.ingen_prototype); + } + + SPtr<PluginModel> plug; + if (p->second.is_valid() && (p->second.type() == _uris.forge.URI || + p->second.type() == _uris.forge.URID)) { + const URI uri(_uris.forge.str(p->second, false)); + if (!(plug = _plugin(uri))) { + plug = SPtr<PluginModel>( + new PluginModel(uris(), uri, Atom(), Properties())); + add_plugin(plug); + } + + SPtr<BlockModel> bm(new BlockModel(uris(), plug, path)); + bm->set_properties(properties); + add_object(bm); + } else { + _log.warn(fmt("Block %1% has no prototype\n") % path.c_str()); + } + } else if (is_port) { + PortModel::Direction pdir = (is_output) + ? PortModel::Direction::OUTPUT + : PortModel::Direction::INPUT; + uint32_t index = 0; + const Iterator i = properties.find(_uris.lv2_index); + if (i != properties.end() && i->second.type() == _uris.forge.Int) { + index = i->second.get<int32_t>(); + } + + SPtr<PortModel> p(new PortModel(uris(), path, index, pdir)); + p->set_properties(properties); + add_object(p); + } else { + _log.warn(fmt("Ignoring %1% of unknown type\n") % path.c_str()); + } +} + +void +ClientStore::operator()(const Delta& msg) +{ + const auto& uri = msg.uri; + if (uri == URI("ingen:/clients/this")) { + // Client property, which we don't store (yet?) + return; + } + + if (!uri_is_path(uri)) { + _log.error(fmt("Delta for unknown subject <%1%>\n") + % uri.c_str()); + return; + } + + const Raul::Path path(uri_to_path(uri)); + + SPtr<ObjectModel> obj = _object(path); + if (obj) { + obj->remove_properties(msg.remove); + obj->add_properties(msg.add); + } else { + _log.warn(fmt("Failed to find object `%1%'\n") % path.c_str()); + } +} + +void +ClientStore::operator()(const SetProperty& msg) +{ + const auto& subject_uri = msg.subject; + const auto& predicate = msg.predicate; + const auto& value = msg.value; + + if (subject_uri == URI("ingen:/engine")) { + _log.info(fmt("Engine property <%1%> = %2%\n") + % predicate.c_str() % _uris.forge.str(value, false)); + return; + } + SPtr<Resource> subject = _resource(subject_uri); + if (subject) { + if (predicate == _uris.ingen_activity) { + /* Activity is transient, trigger any live actions (like GUI + blinkenlights) but do not store the property. */ + subject->on_property(predicate, value); + } else { + subject->set_property(predicate, value, msg.ctx); + } + } else { + SPtr<PluginModel> plugin = _plugin(subject_uri); + if (plugin) { + plugin->set_property(predicate, value); + } else if (predicate != _uris.ingen_activity) { + _log.warn(fmt("Property <%1%> for unknown object %2%\n") + % predicate.c_str() % subject_uri.c_str()); + } + } +} + +SPtr<GraphModel> +ClientStore::connection_graph(const Raul::Path& tail_path, + const Raul::Path& head_path) +{ + SPtr<GraphModel> graph; + + if (tail_path.parent() == head_path.parent()) { + graph = dynamic_ptr_cast<GraphModel>(_object(tail_path.parent())); + } + + if (!graph && tail_path.parent() == head_path.parent().parent()) { + graph = dynamic_ptr_cast<GraphModel>(_object(tail_path.parent())); + } + + if (!graph && tail_path.parent().parent() == head_path.parent()) { + graph = dynamic_ptr_cast<GraphModel>(_object(head_path.parent())); + } + + if (!graph) { + graph = dynamic_ptr_cast<GraphModel>(_object(tail_path.parent().parent())); + } + + if (!graph) { + _log.error(fmt("Unable to find graph for arc %1% => %2%\n") + % tail_path % head_path); + } + + return graph; +} + +bool +ClientStore::attempt_connection(const Raul::Path& tail_path, + const Raul::Path& head_path) +{ + SPtr<PortModel> tail = dynamic_ptr_cast<PortModel>(_object(tail_path)); + SPtr<PortModel> head = dynamic_ptr_cast<PortModel>(_object(head_path)); + + if (tail && head) { + SPtr<GraphModel> graph = connection_graph(tail_path, head_path); + SPtr<ArcModel> arc(new ArcModel(tail, head)); + graph->add_arc(arc); + return true; + } else { + _log.warn(fmt("Failed to connect %1% => %2%\n") + % tail_path % head_path); + return false; + } +} + +void +ClientStore::operator()(const Connect& msg) +{ + attempt_connection(msg.tail, msg.head); +} + +void +ClientStore::operator()(const Disconnect& msg) +{ + SPtr<PortModel> tail = dynamic_ptr_cast<PortModel>(_object(msg.tail)); + SPtr<PortModel> head = dynamic_ptr_cast<PortModel>(_object(msg.head)); + SPtr<GraphModel> graph = connection_graph(msg.tail, msg.head); + if (graph) { + graph->remove_arc(tail.get(), head.get()); + } +} + +void +ClientStore::operator()(const DisconnectAll& msg) +{ + SPtr<GraphModel> graph = dynamic_ptr_cast<GraphModel>(_object(msg.graph)); + SPtr<ObjectModel> object = _object(msg.path); + + if (!graph || !object) { + _log.error(fmt("Bad disconnect all notification %1% in %2%\n") + % msg.path % msg.graph); + return; + } + + const GraphModel::Arcs arcs = graph->arcs(); + for (auto a : arcs) { + SPtr<ArcModel> arc = dynamic_ptr_cast<ArcModel>(a.second); + if (arc->tail()->parent() == object + || arc->head()->parent() == object + || arc->tail()->path() == msg.path + || arc->head()->path() == msg.path) { + graph->remove_arc(arc->tail().get(), arc->head().get()); + } + } +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/GraphModel.cpp b/src/client/GraphModel.cpp new file mode 100644 index 00000000..0723e59b --- /dev/null +++ b/src/client/GraphModel.cpp @@ -0,0 +1,176 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cassert> + +#include "ingen/URIs.hpp" +#include "ingen/client/ArcModel.hpp" +#include "ingen/client/BlockModel.hpp" +#include "ingen/client/ClientStore.hpp" +#include "ingen/client/GraphModel.hpp" + +namespace Ingen { +namespace Client { + +void +GraphModel::add_child(SPtr<ObjectModel> c) +{ + assert(c->parent().get() == this); + + SPtr<PortModel> pm = dynamic_ptr_cast<PortModel>(c); + if (pm) { + add_port(pm); + return; + } + + SPtr<BlockModel> bm = dynamic_ptr_cast<BlockModel>(c); + if (bm) { + _signal_new_block.emit(bm); + } +} + +bool +GraphModel::remove_child(SPtr<ObjectModel> o) +{ + assert(o->path().is_child_of(path())); + assert(o->parent().get() == this); + + SPtr<PortModel> pm = dynamic_ptr_cast<PortModel>(o); + if (pm) { + remove_arcs_on(pm); + remove_port(pm); + } + + SPtr<BlockModel> bm = dynamic_ptr_cast<BlockModel>(o); + if (bm) { + _signal_removed_block.emit(bm); + } + + return true; +} + +void +GraphModel::remove_arcs_on(SPtr<PortModel> p) +{ + // Remove any connections which referred to this object, + // since they can't possibly exist anymore + for (auto j = _arcs.begin(); j != _arcs.end();) { + auto next = j; + ++next; + + SPtr<ArcModel> arc = dynamic_ptr_cast<ArcModel>(j->second); + if (arc->tail_path().parent() == p->path() + || arc->tail_path() == p->path() + || arc->head_path().parent() == p->path() + || arc->head_path() == p->path()) { + _signal_removed_arc.emit(arc); + _arcs.erase(j); // Cuts our reference + } + j = next; + } +} + +void +GraphModel::clear() +{ + _arcs.clear(); + + BlockModel::clear(); + + assert(_arcs.empty()); + assert(_ports.empty()); +} + +SPtr<ArcModel> +GraphModel::get_arc(const Node* tail, const Node* head) +{ + auto i = _arcs.find(std::make_pair(tail, head)); + if (i != _arcs.end()) { + return dynamic_ptr_cast<ArcModel>(i->second); + } else { + return SPtr<ArcModel>(); + } +} + +/** Add a connection to this graph. + * + * A reference to `arc` is taken, released on deletion or removal. + * If `arc` 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 graph is a fatal error. + */ +void +GraphModel::add_arc(SPtr<ArcModel> arc) +{ + // Store should have 'resolved' the connection already + assert(arc); + assert(arc->tail()); + assert(arc->head()); + assert(arc->tail()->parent()); + assert(arc->head()->parent()); + assert(arc->tail_path() != arc->head_path()); + assert(arc->tail()->parent().get() == this + || arc->tail()->parent()->parent().get() == this); + assert(arc->head()->parent().get() == this + || arc->head()->parent()->parent().get() == this); + + SPtr<ArcModel> existing = get_arc( + arc->tail().get(), arc->head().get()); + + if (existing) { + assert(arc->tail() == existing->tail()); + assert(arc->head() == existing->head()); + } else { + _arcs.emplace(std::make_pair(arc->tail().get(), arc->head().get()), + arc); + _signal_new_arc.emit(arc); + } +} + +void +GraphModel::remove_arc(const Node* tail, const Node* head) +{ + auto i = _arcs.find(std::make_pair(tail, head)); + if (i != _arcs.end()) { + SPtr<ArcModel> arc = dynamic_ptr_cast<ArcModel>(i->second); + _signal_removed_arc.emit(arc); + _arcs.erase(i); + } +} + +bool +GraphModel::enabled() const +{ + const Atom& enabled = get_property(_uris.ingen_enabled); + return (enabled.is_valid() && enabled.get<int32_t>()); +} + +uint32_t +GraphModel::internal_poly() const +{ + const Atom& poly = get_property(_uris.ingen_polyphony); + return poly.is_valid() ? poly.get<int32_t>() : 1; +} + +bool +GraphModel::polyphonic() const +{ + const Atom& poly = get_property(_uris.ingen_polyphonic); + return poly.is_valid() && poly.get<int32_t>(); +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/ObjectModel.cpp b/src/client/ObjectModel.cpp new file mode 100644 index 00000000..8d40b120 --- /dev/null +++ b/src/client/ObjectModel.cpp @@ -0,0 +1,108 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Node.hpp" +#include "ingen/URIs.hpp" +#include "ingen/client/ObjectModel.hpp" + +namespace Ingen { +namespace Client { + +ObjectModel::ObjectModel(URIs& uris, const Raul::Path& path) + : Node(uris, path) + , _path(path) + , _symbol((path == "/") ? "root" : path.symbol()) +{ +} + +ObjectModel::ObjectModel(const ObjectModel& copy) + : Node(copy) + , _parent(copy._parent) + , _path(copy._path) + , _symbol(copy._symbol) +{ +} + +bool +ObjectModel::is_a(const URIs::Quark& type) const +{ + return has_property(_uris.rdf_type, type); +} + +void +ObjectModel::on_property(const URI& uri, const Atom& value) +{ + _signal_property.emit(uri, value); +} + +void +ObjectModel::on_property_removed(const URI& uri, const Atom& value) +{ + _signal_property_removed.emit(uri, value); +} + +const Atom& +ObjectModel::get_property(const URI& key) const +{ + static const Atom null_atom; + auto i = properties().find(key); + return (i != properties().end()) ? i->second : null_atom; +} + +bool +ObjectModel::polyphonic() const +{ + const Atom& polyphonic = get_property(_uris.ingen_polyphonic); + return (polyphonic.is_valid() && polyphonic.get<int32_t>()); +} + +/** Merge the data of `o` with self, as much as possible. + * + * This will merge the two models, but with any conflict take the value in + * `o` as correct. The paths of the two models MUST be equal. + */ +void +ObjectModel::set(SPtr<ObjectModel> o) +{ + assert(_path == o->path()); + if (o->_parent) { + _parent = o->_parent; + } + + for (auto v : o->properties()) { + Resource::set_property(v.first, v.second); + _signal_property.emit(v.first, v.second); + } +} + +void +ObjectModel::set_path(const Raul::Path& p) +{ + _path = p; + _symbol = Raul::Symbol(p.is_root() ? "root" : p.symbol()); + set_uri(path_to_uri(p)); + _signal_moved.emit(); +} + +void +ObjectModel::set_parent(SPtr<ObjectModel> p) +{ + assert(_path.is_child_of(p->path())); + _parent = p; +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/PluginModel.cpp b/src/client/PluginModel.cpp new file mode 100644 index 00000000..5427b75e --- /dev/null +++ b/src/client/PluginModel.cpp @@ -0,0 +1,360 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> +#include <algorithm> + +#include <boost/optional.hpp> + +#include "raul/Path.hpp" + +#include "ingen/Atom.hpp" +#include "ingen/client/GraphModel.hpp" +#include "ingen/client/PluginModel.hpp" +#include "ingen/client/PluginUI.hpp" + +#include "ingen_config.h" + +using std::string; + +namespace Ingen { +namespace Client { + +LilvWorld* PluginModel::_lilv_world = nullptr; +const LilvPlugins* PluginModel::_lilv_plugins = nullptr; + +Sord::World* PluginModel::_rdf_world = nullptr; + +PluginModel::PluginModel(URIs& uris, + const URI& uri, + const Atom& type, + const Properties& properties) + : Resource(uris, uri) + , _type(type) + , _fetched(false) +{ + if (!_type.is_valid()) { + if (uri.string().find("ingen-internals") != string::npos) { + _type = uris.ingen_Internal.urid; + } else { + _type = uris.lv2_Plugin.urid; // Assume LV2 and hope for the best... + } + } + + add_property(uris.rdf_type, type); + add_properties(properties); + + LilvNode* plugin_uri = lilv_new_uri(_lilv_world, uri.c_str()); + _lilv_plugin = lilv_plugins_get_by_uri(_lilv_plugins, plugin_uri); + lilv_node_free(plugin_uri); + + if (uris.ingen_Internal == _type) { + set_property(uris.doap_name, + uris.forge.alloc(std::string(uri.fragment().substr(1)))); + } +} + +static size_t +last_uri_delim(const std::string& str) +{ + for (size_t i = str.length() - 1; i > 0; --i) { + switch (str[i]) { + case ':': case '/': case '?': case '#': + return i; + } + } + return string::npos; +} + +static bool +contains_alpha_after(const std::string& str, size_t begin) +{ + for (size_t i = begin; i < str.length(); ++i) { + if (isalpha(str[i])) { + return true; + } + } + return false; +} + +const Atom& +PluginModel::get_property(const URI& key) const +{ + static const Atom nil; + const Atom& val = Resource::get_property(key); + if (val.is_valid()) { + return val; + } + + // No lv2:symbol from data or engine, invent one + if (key == _uris.lv2_symbol) { + string str = this->uri(); + size_t last_delim = last_uri_delim(str); + while (last_delim != string::npos && + !contains_alpha_after(str, last_delim)) { + str = str.substr(0, last_delim); + last_delim = last_uri_delim(str); + } + str = str.substr(last_delim + 1); + + std::string symbol = Raul::Symbol::symbolify(str); + set_property(_uris.lv2_symbol, _uris.forge.alloc(symbol)); + return get_property(key); + } + + if (_lilv_plugin) { + boost::optional<const Atom&> ret; + LilvNode* lv2_pred = lilv_new_uri(_lilv_world, key.c_str()); + LilvNodes* values = lilv_plugin_get_value(_lilv_plugin, lv2_pred); + lilv_node_free(lv2_pred); + LILV_FOREACH(nodes, i, values) { + const LilvNode* val = lilv_nodes_get(values, i); + if (lilv_node_is_uri(val)) { + ret = set_property( + key, _uris.forge.make_urid(URI(lilv_node_as_uri(val)))); + break; + } else if (lilv_node_is_string(val)) { + ret = set_property( + key, _uris.forge.alloc(lilv_node_as_string(val))); + break; + } else if (lilv_node_is_float(val)) { + ret = set_property( + key, _uris.forge.make(lilv_node_as_float(val))); + break; + } else if (lilv_node_is_int(val)) { + ret = set_property( + key, _uris.forge.make(lilv_node_as_int(val))); + break; + } + } + lilv_nodes_free(values); + + if (ret) { + return *ret; + } + } + + return nil; +} + +void +PluginModel::set(SPtr<PluginModel> p) +{ + _type = p->_type; + + if (p->_lilv_plugin) { + _lilv_plugin = p->_lilv_plugin; + } + + for (auto v : p->properties()) { + Resource::set_property(v.first, v.second); + _signal_property.emit(v.first, v.second); + } + + _signal_changed.emit(); +} + +void +PluginModel::add_preset(const URI& uri, const std::string& label) +{ + _presets.emplace(uri, label); + _signal_preset.emit(uri, label); +} + +Raul::Symbol +PluginModel::default_block_symbol() const +{ + const Atom& name_atom = get_property(_uris.lv2_symbol); + if (name_atom.is_valid() && name_atom.type() == _uris.forge.String) { + return Raul::Symbol::symbolify(name_atom.ptr<char>()); + } else { + return Raul::Symbol("_"); + } +} + +string +PluginModel::human_name() const +{ + const Atom& name_atom = get_property(_uris.doap_name); + if (name_atom.type() == _uris.forge.String) { + return name_atom.ptr<char>(); + } else { + return default_block_symbol().c_str(); + } +} + +string +PluginModel::port_human_name(uint32_t i) const +{ + if (_lilv_plugin) { + const LilvPort* port = lilv_plugin_get_port_by_index(_lilv_plugin, i); + LilvNode* name = lilv_port_get_name(_lilv_plugin, port); + const string ret(lilv_node_as_string(name)); + lilv_node_free(name); + return ret; + } + return ""; +} + +PluginModel::ScalePoints +PluginModel::port_scale_points(uint32_t i) const +{ + // TODO: Non-float scale points + ScalePoints points; + if (_lilv_plugin) { + const LilvPort* port = lilv_plugin_get_port_by_index(_lilv_plugin, i); + LilvScalePoints* sp = lilv_port_get_scale_points(_lilv_plugin, port); + LILV_FOREACH(scale_points, i, sp) { + const LilvScalePoint* p = lilv_scale_points_get(sp, i); + points.emplace( + lilv_node_as_float(lilv_scale_point_get_value(p)), + lilv_node_as_string(lilv_scale_point_get_label(p))); + } + } + return points; +} + +bool +PluginModel::has_ui() const +{ + if (_lilv_plugin) { + LilvUIs* uis = lilv_plugin_get_uis(_lilv_plugin); + const bool ret = (lilv_nodes_size(uis) > 0); + lilv_uis_free(uis); + return ret; + } + return false; +} + +SPtr<PluginUI> +PluginModel::ui(Ingen::World* world, + SPtr<const BlockModel> block) const +{ + if (!_lilv_plugin) { + return SPtr<PluginUI>(); + } + + return PluginUI::create(world, block, _lilv_plugin); +} + +static std::string +heading(const std::string& text, bool html, unsigned level) +{ + if (html) { + const std::string tag = std::string("h") + std::to_string(level); + return std::string("<") + tag + ">" + text + "</" + tag + ">\n"; + } else { + return text + ":\n\n"; + } +} + +static std::string +link(const std::string& addr, bool html) +{ + if (html) { + return std::string("<a href=\"") + addr + "\">" + addr + "</a>"; + } else { + return addr; + } +} + +std::string +PluginModel::get_documentation(const LilvNode* subject, bool html) const +{ + std::string doc; + + LilvNode* lv2_documentation = lilv_new_uri(_lilv_world, + LV2_CORE__documentation); + LilvNode* rdfs_comment = lilv_new_uri(_lilv_world, + LILV_NS_RDFS "comment"); + + LilvNodes* vals = lilv_world_find_nodes( + _lilv_world, subject, lv2_documentation, nullptr); + const bool doc_is_html = vals; + if (!vals) { + vals = lilv_world_find_nodes( + _lilv_world, subject, rdfs_comment, nullptr); + } + + if (vals) { + const LilvNode* val = lilv_nodes_get_first(vals); + if (lilv_node_is_string(val)) { + doc += lilv_node_as_string(val); + } + } + + if (html && !doc_is_html) { + for (std::size_t i = 0; i < doc.size(); ++i) { + if (doc.substr(i, 2) == "\n\n") { + doc.replace(i, 2, "<br/><br/>"); + i += strlen("<br/><br/>"); + } + } + } + + lilv_node_free(rdfs_comment); + lilv_node_free(lv2_documentation); + + return doc; +} + +std::string +PluginModel::documentation(const bool html) const +{ + LilvNode* subject = (_lilv_plugin) + ? lilv_node_duplicate(lilv_plugin_get_uri(_lilv_plugin)) + : lilv_new_uri(_lilv_world, uri().c_str()); + + const std::string doc(get_documentation(subject, html)); + + lilv_node_free(subject); + + return (heading(human_name(), html, 2) + + link(uri(), html) + (html ? "<br/><br/>" : "\n\n") + + doc); +} + +std::string +PluginModel::port_documentation(uint32_t index, bool html) const +{ + if (!_lilv_plugin) { + return ""; + } + + const LilvPort* port = lilv_plugin_get_port_by_index(_lilv_plugin, index); + if (!port) { + return ""; + } + + return (heading(port_human_name(index), html, 2) + + get_documentation(lilv_port_get_node(_lilv_plugin, port), html)); +} + +const LilvPort* +PluginModel::lilv_port(uint32_t index) const +{ + return lilv_plugin_get_port_by_index(_lilv_plugin, index); +} + +void +PluginModel::set_lilv_world(LilvWorld* world) +{ + _lilv_world = world; + _lilv_plugins = lilv_world_get_all_plugins(_lilv_world); +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/PluginUI.cpp b/src/client/PluginUI.cpp new file mode 100644 index 00000000..df983f7f --- /dev/null +++ b/src/client/PluginUI.cpp @@ -0,0 +1,336 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Interface.hpp" +#include "ingen/Log.hpp" +#include "ingen/URIs.hpp" +#include "ingen/client/BlockModel.hpp" +#include "ingen/client/PluginUI.hpp" +#include "ingen/client/PortModel.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +namespace Ingen { +namespace Client { + +SuilHost* PluginUI::ui_host = nullptr; + +static SPtr<const PortModel> +get_port(PluginUI* ui, uint32_t port_index) +{ + if (port_index >= ui->block()->ports().size()) { + ui->world()->log().error( + fmt("%1% UI tried to access invalid port %2%\n") + % ui->block()->plugin()->uri().c_str() % port_index); + return SPtr<const PortModel>(); + } + return ui->block()->ports()[port_index]; +} + +static void +lv2_ui_write(SuilController controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + PluginUI* const ui = (PluginUI*)controller; + const URIs& uris = ui->world()->uris(); + SPtr<const PortModel> port = get_port(ui, port_index); + if (!port) { + return; + } + + // float (special case, always 0) + if (format == 0) { + if (buffer_size != 4) { + ui->world()->log().error( + fmt("%1% UI wrote corrupt float with bad size\n") + % ui->block()->plugin()->uri().c_str()); + return; + } + const float value = *(const float*)buffer; + if (port->value().type() == uris.atom_Float && + value == port->value().get<float>()) { + return; // Ignore feedback + } + + ui->signal_property_changed()( + port->uri(), + uris.ingen_value, + ui->world()->forge().make(value), + Resource::Graph::DEFAULT); + + } else if (format == uris.atom_eventTransfer.urid.get<LV2_URID>()) { + const LV2_Atom* atom = (const LV2_Atom*)buffer; + Atom val = ui->world()->forge().alloc( + atom->size, atom->type, LV2_ATOM_BODY_CONST(atom)); + ui->signal_property_changed()(port->uri(), + uris.ingen_activity, + val, + Resource::Graph::DEFAULT); + } else { + ui->world()->log().warn( + fmt("Unknown value format %1% from LV2 UI\n") + % format % ui->block()->plugin()->uri().c_str()); + } +} + +static uint32_t +lv2_ui_port_index(SuilController controller, const char* port_symbol) +{ + PluginUI* const ui = (PluginUI*)controller; + + const BlockModel::Ports& ports = ui->block()->ports(); + for (uint32_t i = 0; i < ports.size(); ++i) { + if (ports[i]->symbol() == port_symbol) { + return i; + } + } + return LV2UI_INVALID_PORT_INDEX; +} + +static uint32_t +lv2_ui_subscribe(SuilController controller, + uint32_t port_index, + uint32_t protocol, + const LV2_Feature* const* features) +{ + PluginUI* const ui = (PluginUI*)controller; + SPtr<const PortModel> port = get_port(ui, port_index); + if (!port) { + return 1; + } + + ui->signal_property_changed()( + ui->block()->ports()[port_index]->uri(), + ui->world()->uris().ingen_broadcast, + ui->world()->forge().make(true), + Resource::Graph::DEFAULT); + + return 0; +} + +static uint32_t +lv2_ui_unsubscribe(SuilController controller, + uint32_t port_index, + uint32_t protocol, + const LV2_Feature* const* features) +{ + PluginUI* const ui = (PluginUI*)controller; + SPtr<const PortModel> port = get_port(ui, port_index); + if (!port) { + return 1; + } + + ui->signal_property_changed()( + ui->block()->ports()[port_index]->uri(), + ui->world()->uris().ingen_broadcast, + ui->world()->forge().make(false), + Resource::Graph::DEFAULT); + + return 0; +} + +PluginUI::PluginUI(Ingen::World* world, + SPtr<const BlockModel> block, + LilvUIs* uis, + const LilvUI* ui, + const LilvNode* ui_type) + : _world(world) + , _block(std::move(block)) + , _instance(nullptr) + , _uis(uis) + , _ui(ui) + , _ui_node(lilv_node_duplicate(lilv_ui_get_uri(ui))) + , _ui_type(lilv_node_duplicate(ui_type)) +{ +} + +PluginUI::~PluginUI() +{ + for (uint32_t i : _subscribed_ports) { + lv2_ui_unsubscribe(this, i, 0, nullptr); + } + suil_instance_free(_instance); + lilv_node_free(_ui_node); + lilv_node_free(_ui_type); + lilv_uis_free(_uis); + lilv_world_unload_resource(_world->lilv_world(), lilv_ui_get_uri(_ui)); +} + +SPtr<PluginUI> +PluginUI::create(Ingen::World* world, + SPtr<const BlockModel> block, + const LilvPlugin* plugin) +{ + if (!PluginUI::ui_host) { + PluginUI::ui_host = suil_host_new(lv2_ui_write, + lv2_ui_port_index, + lv2_ui_subscribe, + lv2_ui_unsubscribe); + } + + static const char* gtk_ui_uri = LV2_UI__GtkUI; + + LilvNode* gtk_ui = lilv_new_uri(world->lilv_world(), gtk_ui_uri); + + LilvUIs* uis = lilv_plugin_get_uis(plugin); + const LilvUI* ui = nullptr; + const LilvNode* ui_type = nullptr; + LILV_FOREACH(uis, u, uis) { + const LilvUI* this_ui = lilv_uis_get(uis, u); + if (lilv_ui_is_supported(this_ui, + suil_ui_supported, + gtk_ui, + &ui_type)) { + // TODO: Multiple UI support + ui = this_ui; + break; + } + } + + if (!ui) { + lilv_node_free(gtk_ui); + return SPtr<PluginUI>(); + } + + // Create the PluginUI, but don't instantiate yet + SPtr<PluginUI> ret(new PluginUI(world, block, uis, ui, ui_type)); + ret->_features = world->lv2_features().lv2_features( + world, const_cast<BlockModel*>(block.get())); + + return ret; +} + +bool +PluginUI::instantiate() +{ + const URIs& uris = _world->uris(); + const std::string plugin_uri = _block->plugin()->uri(); + LilvWorld* lworld = _world->lilv_world(); + + // Load seeAlso files to access data like portNotification descriptions + lilv_world_load_resource(lworld, lilv_ui_get_uri(_ui)); + + /* Subscribe (enable broadcast) for any requested port notifications. This + must be done before instantiation so responses to any events sent by the + UI's init() will be sent back to this client. */ + LilvNode* ui_portNotification = lilv_new_uri(lworld, LV2_UI__portNotification); + LilvNode* ui_plugin = lilv_new_uri(lworld, LV2_UI__plugin); + LilvNodes* notes = lilv_world_find_nodes( + lworld, lilv_ui_get_uri(_ui), ui_portNotification, nullptr); + LILV_FOREACH(nodes, n, notes) { + const LilvNode* note = lilv_nodes_get(notes, n); + const LilvNode* sym = lilv_world_get(lworld, note, uris.lv2_symbol, nullptr); + const LilvNode* plug = lilv_world_get(lworld, note, ui_plugin, nullptr); + if (!plug) { + _world->log().error(fmt("%1% UI %2% notification missing plugin\n") + % plugin_uri % lilv_node_as_string(_ui_node)); + } else if (!sym) { + _world->log().error(fmt("%1% UI %2% notification missing symbol\n") + % plugin_uri % lilv_node_as_string(_ui_node)); + } else if (!lilv_node_is_uri(plug)) { + _world->log().error(fmt("%1% UI %2% notification has non-URI plugin\n") + % plugin_uri % lilv_node_as_string(_ui_node)); + } else if (!strcmp(lilv_node_as_uri(plug), plugin_uri.c_str())) { + // Notification is valid and for this plugin + uint32_t index = lv2_ui_port_index(this, lilv_node_as_string(sym)); + if (index != LV2UI_INVALID_PORT_INDEX) { + lv2_ui_subscribe(this, index, 0, nullptr); + _subscribed_ports.insert(index); + } + } + } + lilv_nodes_free(notes); + lilv_node_free(ui_plugin); + lilv_node_free(ui_portNotification); + + const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(_ui)); + const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(_ui)); + char* bundle_path = lilv_file_uri_parse(bundle_uri, nullptr); + char* binary_path = lilv_file_uri_parse(binary_uri, nullptr); + + // Instantiate the actual plugin UI via Suil + _instance = suil_instance_new( + PluginUI::ui_host, + this, + LV2_UI__GtkUI, + plugin_uri.c_str(), + lilv_node_as_uri(lilv_ui_get_uri(_ui)), + lilv_node_as_uri(_ui_type), + bundle_path, + binary_path, + _features->array()); + + lilv_free(binary_path); + lilv_free(bundle_path); + + if (!_instance) { + _world->log().error("Failed to instantiate LV2 UI\n"); + // Cancel any subscriptions + for (uint32_t i : _subscribed_ports) { + lv2_ui_unsubscribe(this, i, 0, nullptr); + } + return false; + } + + return true; +} + +SuilWidget +PluginUI::get_widget() +{ + return (SuilWidget*)suil_instance_get_widget(_instance); +} + +void +PluginUI::port_event(uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + if (_instance) { + suil_instance_port_event( + _instance, port_index, buffer_size, format, buffer); + } else { + _world->log().warn("LV2 UI port event with no instance\n"); + } +} + +bool +PluginUI::is_resizable() const +{ + LilvWorld* w = _world->lilv_world(); + const LilvNode* s = _ui_node; + LilvNode* p = lilv_new_uri(w, LV2_CORE__optionalFeature); + LilvNode* fs = lilv_new_uri(w, LV2_UI__fixedSize); + LilvNode* nrs = lilv_new_uri(w, LV2_UI__noUserResize); + + LilvNodes* fs_matches = lilv_world_find_nodes(w, s, p, fs); + LilvNodes* nrs_matches = lilv_world_find_nodes(w, s, p, nrs); + + lilv_nodes_free(nrs_matches); + lilv_nodes_free(fs_matches); + lilv_node_free(nrs); + lilv_node_free(fs); + lilv_node_free(p); + + return !fs_matches && !nrs_matches; +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/PortModel.cpp b/src/client/PortModel.cpp new file mode 100644 index 00000000..5c9a8c77 --- /dev/null +++ b/src/client/PortModel.cpp @@ -0,0 +1,78 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/client/BlockModel.hpp" +#include "ingen/client/PortModel.hpp" + +namespace Ingen { +namespace Client { + +void +PortModel::on_property(const URI& uri, const Atom& value) +{ + if (uri == _uris.ingen_activity) { + // Don't store activity, it is transient + signal_activity().emit(value); + return; + } + + ObjectModel::on_property(uri, value); + + if (uri == _uris.ingen_value) { + signal_value_changed().emit(value); + } +} + +bool +PortModel::supports(const URIs::Quark& value_type) const +{ + return has_property(_uris.atom_supports, value_type); +} + +bool +PortModel::port_property(const URIs::Quark& uri) const +{ + return has_property(_uris.lv2_portProperty, uri); +} + +bool +PortModel::is_uri() const +{ + // FIXME: Resource::has_property doesn't work, URI != URID + for (auto p : properties()) { + if (p.second.type() == _uris.atom_URID && + static_cast<LV2_URID>(p.second.get<int32_t>()) == _uris.atom_URID) { + return true; + } + } + return false; +} + +void +PortModel::set(SPtr<ObjectModel> model) +{ + ObjectModel::set(model); + + SPtr<PortModel> port = dynamic_ptr_cast<PortModel>(model); + if (port) { + _index = port->_index; + _direction = port->_direction; + _signal_value_changed.emit(get_property(_uris.ingen_value)); + } +} + +} // namespace Client +} // namespace Ingen diff --git a/src/client/ingen_client.cpp b/src/client/ingen_client.cpp new file mode 100644 index 00000000..fe9d6605 --- /dev/null +++ b/src/client/ingen_client.cpp @@ -0,0 +1,34 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Module.hpp" +#include "ingen/World.hpp" + +#include "ingen_config.h" + +struct IngenClientModule : public Ingen::Module { + void load(Ingen::World* world) {} +}; + +extern "C" { + +Ingen::Module* +ingen_module_load() +{ + return new IngenClientModule(); +} + +} // extern "C" diff --git a/src/client/wscript b/src/client/wscript new file mode 100644 index 00000000..df575c0d --- /dev/null +++ b/src/client/wscript @@ -0,0 +1,23 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + obj = bld(features = 'cxx cxxshlib', + includes = ['../..'], + export_includes = ['../..'], + name = 'libingen_client', + target = 'ingen_client', + install_path = '${LIBDIR}', + use = 'libingen') + autowaf.use_lib(bld, obj, 'GLIBMM LV2 LILV SUIL RAUL SERD SORD SIGCPP') + + obj.source = ''' + BlockModel.cpp + ClientStore.cpp + GraphModel.cpp + ObjectModel.cpp + PluginModel.cpp + PluginUI.cpp + PortModel.cpp + ingen_client.cpp + ''' |