From bb64f80bb139314a06e0b22fddbea7a330b6e149 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 18 Sep 2016 21:36:48 -0400 Subject: Preliminary parameter support work --- ingen/Node.hpp | 3 +- ingen/Resource.hpp | 1 + ingen/URIs.hpp | 4 + ingen/client/BlockModel.hpp | 11 +- ingen/client/ParameterModel.hpp | 63 +++++ ingen/ingen.h | 1 + src/Resource.cpp | 5 +- src/Store.cpp | 2 + src/URIs.cpp | 4 + src/client/BlockModel.cpp | 31 ++- src/client/ClientStore.cpp | 9 +- src/gui/NodeModule.cpp | 30 ++- src/gui/NodeModule.hpp | 2 + src/gui/Parameter.cpp | 520 ++++++++++++++++++++++++++++++++++++++++ src/gui/Parameter.hpp | 102 ++++++++ src/gui/wscript | 1 + src/server/BlockImpl.hpp | 6 + src/server/ClientUpdate.cpp | 15 ++ src/server/ClientUpdate.hpp | 4 +- src/server/LV2Block.cpp | 20 +- src/server/Parameter.hpp | 82 +++++++ src/server/events/Delta.cpp | 4 +- 22 files changed, 904 insertions(+), 16 deletions(-) create mode 100644 ingen/client/ParameterModel.hpp create mode 100644 src/gui/Parameter.cpp create mode 100644 src/gui/Parameter.hpp create mode 100644 src/server/Parameter.hpp diff --git a/ingen/Node.hpp b/ingen/Node.hpp index fb5fc985..c5b905bd 100644 --- a/ingen/Node.hpp +++ b/ingen/Node.hpp @@ -52,7 +52,8 @@ public: enum class GraphType { GRAPH, BLOCK, - PORT + PORT, + PARAMETER }; typedef std::pair ArcsKey; diff --git a/ingen/Resource.hpp b/ingen/Resource.hpp index 604428ce..ddb3078a 100644 --- a/ingen/Resource.hpp +++ b/ingen/Resource.hpp @@ -240,6 +240,7 @@ public: bool& graph, bool& block, bool& port, + bool& parameter, bool& is_output); virtual void set_uri(const Raul::URI& uri) { _uri = uri; } diff --git a/ingen/URIs.hpp b/ingen/URIs.hpp index 9fc771da..1a05ab2d 100644 --- a/ingen/URIs.hpp +++ b/ingen/URIs.hpp @@ -105,6 +105,7 @@ public: const Quark ingen_Graph; const Quark ingen_GraphPrototype; const Quark ingen_Internal; + const Quark ingen_Parameter; const Quark ingen_Redo; const Quark ingen_Undo; const Quark ingen_activity; @@ -183,17 +184,20 @@ public: const Quark patch_body; const Quark patch_destination; const Quark patch_property; + const Quark patch_readable; const Quark patch_remove; const Quark patch_sequenceNumber; const Quark patch_subject; const Quark patch_value; const Quark patch_wildcard; + const Quark patch_writable; const Quark pprops_logarithmic; const Quark pset_Preset; const Quark pset_preset; const Quark rdf_type; const Quark rdfs_Class; const Quark rdfs_label; + const Quark rdfs_range; const Quark rdfs_seeAlso; const Quark rsz_minimumSize; const Quark state_loadDefaultState; diff --git a/ingen/client/BlockModel.hpp b/ingen/client/BlockModel.hpp index e689414c..a9474671 100644 --- a/ingen/client/BlockModel.hpp +++ b/ingen/client/BlockModel.hpp @@ -36,8 +36,9 @@ class URIs; namespace Client { -class PluginModel; class ClientStore; +class ParameterModel; +class PluginModel; /** Block model class, used by the client to store engine's state. * @@ -51,7 +52,8 @@ public: GraphType graph_type() const { return Node::GraphType::BLOCK; } - typedef std::vector< SPtr > Ports; + typedef std::vector< SPtr > Ports; + typedef std::vector< SPtr > Parameters; SPtr get_port(const Raul::Symbol& symbol) const; SPtr get_port(uint32_t index) const; @@ -64,6 +66,7 @@ public: SPtr plugin_model() const { return _plugin; } uint32_t num_ports() const { return _ports.size(); } const Ports& ports() const { return _ports; } + const Parameters& parameters() const { return _parameters; } void default_port_value_range(SPtr port, float& min, @@ -81,6 +84,8 @@ public: // Signals INGEN_SIGNAL(new_port, void, SPtr); INGEN_SIGNAL(removed_port, void, SPtr); + INGEN_SIGNAL(new_parameter, void, SPtr); + INGEN_SIGNAL(removed_parameter, void, SPtr); protected: friend class ClientStore; @@ -98,11 +103,13 @@ protected: void add_port(SPtr pm); void remove_port(SPtr pm); void remove_port(const Raul::Path& port_path); + void add_parameter(SPtr pm); void set(SPtr model); virtual void clear(); Ports _ports; ///< Vector of ports + Parameters _parameters; ///< Vector of parameters Raul::URI _plugin_uri; ///< Plugin URI (if PluginModel is unknown) SPtr _plugin; ///< The plugin this block is an instance of diff --git a/ingen/client/ParameterModel.hpp b/ingen/client/ParameterModel.hpp new file mode 100644 index 00000000..692c0108 --- /dev/null +++ b/ingen/client/ParameterModel.hpp @@ -0,0 +1,63 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + 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 . +*/ + +#ifndef INGEN_CLIENT_PARAMETERMODEL_HPP +#define INGEN_CLIENT_PARAMETERMODEL_HPP + +#include +#include + +#include "ingen/client/ObjectModel.hpp" + +namespace Raul { class Path; } + +namespace Ingen { +namespace Client { + +/** Model of a parameter. + * + * @ingroup IngenClient + */ +class INGEN_API ParameterModel : public ObjectModel +{ +public: + GraphType graph_type() const { return Node::GraphType::PARAMETER; } + + inline const Atom& value() const { return get_property(_uris.ingen_value); } + inline bool is_input() const { return true; } + inline bool is_output() const { return true; } + + inline bool operator==(const ParameterModel& pm) const { return (path() == pm.path()); } + + void on_property(const Raul::URI& uri, const Atom& value) {} + + INGEN_SIGNAL(value_changed, void, const Atom&); + +private: + friend class ClientStore; + + ParameterModel(URIs& uris, + const Raul::Path& path) + : ObjectModel(uris, path) + {} + + void set(SPtr model) {} +}; + +} // namespace Client +} // namespace Ingen + +#endif // INGEN_CLIENT_PARAMETERMODEL_HPP diff --git a/ingen/ingen.h b/ingen/ingen.h index 3af2042c..f1735f32 100644 --- a/ingen/ingen.h +++ b/ingen/ingen.h @@ -44,6 +44,7 @@ #define INGEN__GraphPrototype INGEN_NS "GraphPrototype" #define INGEN__Internal INGEN_NS "Internal" #define INGEN__Node INGEN_NS "Node" +#define INGEN__Parameter INGEN_NS "Parameter" #define INGEN__Plugin INGEN_NS "Plugin" #define INGEN__Redo INGEN_NS "Redo" #define INGEN__Undo INGEN_NS "Undo" diff --git a/src/Resource.cpp b/src/Resource.cpp index eef5e063..a88ddd1f 100644 --- a/src/Resource.cpp +++ b/src/Resource.cpp @@ -139,6 +139,7 @@ Resource::type(const URIs& uris, bool& graph, bool& block, bool& port, + bool& parameter, bool& is_output) { typedef Resource::Properties::const_iterator iterator; @@ -155,6 +156,8 @@ Resource::type(const URIs& uris, graph = true; } else if (uris.ingen_Block == atom) { block = true; + } else if (uris.ingen_Parameter == atom) { + parameter = true; } else if (uris.lv2_InputPort == atom) { port = true; is_output = false; @@ -170,7 +173,7 @@ Resource::type(const URIs& uris, } else if (port && (graph || block)) { // nonsense port = false; return false; - } else if (graph || block || port) { // recognized type + } else if (graph || block || port || parameter) { // recognized type return true; } else { // unknown return false; diff --git a/src/Store.cpp b/src/Store.cpp index 1767b4f5..590f0d0b 100644 --- a/src/Store.cpp +++ b/src/Store.cpp @@ -34,6 +34,8 @@ Store::add(Node* o) for (uint32_t i = 0; i < o->num_ports(); ++i) { add(o->port(i)); } + + // FIXME: add parameters } /* diff --git a/src/URIs.cpp b/src/URIs.cpp index b272e48e..bf256649 100644 --- a/src/URIs.cpp +++ b/src/URIs.cpp @@ -88,6 +88,7 @@ URIs::URIs(Forge& f, URIMap* map, LilvWorld* lworld) , ingen_Graph (forge, map, lworld, INGEN__Graph) , ingen_GraphPrototype (forge, map, lworld, INGEN__GraphPrototype) , ingen_Internal (forge, map, lworld, INGEN__Internal) + , ingen_Parameter (forge, map, lworld, INGEN__Parameter) , ingen_Redo (forge, map, lworld, INGEN__Redo) , ingen_Undo (forge, map, lworld, INGEN__Undo) , ingen_activity (forge, map, lworld, INGEN__activity) @@ -166,17 +167,20 @@ URIs::URIs(Forge& f, URIMap* map, LilvWorld* lworld) , patch_body (forge, map, lworld, LV2_PATCH__body) , patch_destination (forge, map, lworld, LV2_PATCH__destination) , patch_property (forge, map, lworld, LV2_PATCH__property) + , patch_readable (forge, map, lworld, LV2_PATCH__readable) , patch_remove (forge, map, lworld, LV2_PATCH__remove) , patch_sequenceNumber (forge, map, lworld, LV2_PATCH__sequenceNumber) , patch_subject (forge, map, lworld, LV2_PATCH__subject) , patch_value (forge, map, lworld, LV2_PATCH__value) , patch_wildcard (forge, map, lworld, LV2_PATCH__wildcard) + , patch_writable (forge, map, lworld, LV2_PATCH__writable) , pprops_logarithmic (forge, map, lworld, LV2_PORT_PROPS__logarithmic) , pset_Preset (forge, map, lworld, LV2_PRESETS__Preset) , pset_preset (forge, map, lworld, LV2_PRESETS__preset) , rdf_type (forge, map, lworld, NS_RDF "type") , rdfs_Class (forge, map, lworld, NS_RDFS "Class") , rdfs_label (forge, map, lworld, NS_RDFS "label") + , rdfs_range (forge, map, lworld, NS_RDFS "range") , rdfs_seeAlso (forge, map, lworld, NS_RDFS "seeAlso") , rsz_minimumSize (forge, map, lworld, LV2_RESIZE_PORT__minimumSize) , state_loadDefaultState(forge, map, lworld, LV2_STATE__loadDefaultState) diff --git a/src/client/BlockModel.cpp b/src/client/BlockModel.cpp index e3f7d22f..95092701 100644 --- a/src/client/BlockModel.cpp +++ b/src/client/BlockModel.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard 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 @@ -18,9 +18,10 @@ #include #include -#include "ingen/client/BlockModel.hpp" #include "ingen/URIs.hpp" #include "ingen/World.hpp" +#include "ingen/client/BlockModel.hpp" +#include "ingen/client/ParameterModel.hpp" namespace Ingen { namespace Client { @@ -106,8 +107,16 @@ BlockModel::add_child(SPtr c) //ObjectModel::add_child(c); SPtr pm = dynamic_ptr_cast(c); - assert(pm); - add_port(pm); + if (pm) { + add_port(pm); + return; + } + + SPtr am = dynamic_ptr_cast(c); + if (am) { + add_parameter(am); + return; + } } bool @@ -140,6 +149,20 @@ BlockModel::add_port(SPtr pm) _signal_new_port.emit(pm); } +void +BlockModel::add_parameter(SPtr 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(_parameters.begin(), _parameters.end(), pm) == _parameters.end()); + + _parameters.push_back(pm); + _signal_new_parameter.emit(pm); +} + SPtr BlockModel::get_port(const Raul::Symbol& symbol) const { diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp index e99f9c73..3755772c 100644 --- a/src/client/ClientStore.cpp +++ b/src/client/ClientStore.cpp @@ -20,6 +20,7 @@ #include "ingen/client/ClientStore.hpp" #include "ingen/client/GraphModel.hpp" #include "ingen/client/ObjectModel.hpp" +#include "ingen/client/ParameterModel.hpp" #include "ingen/client/PluginModel.hpp" #include "ingen/client/PortModel.hpp" #include "ingen/client/SigClientInterface.hpp" @@ -243,9 +244,9 @@ ClientStore::put(const Raul::URI& uri, { typedef Resource::Properties::const_iterator Iterator; - bool is_graph, is_block, is_port, is_output; + bool is_graph, is_block, is_port, is_parameter, is_output; Resource::type(uris(), properties, - is_graph, is_block, is_port, is_output); + is_graph, is_block, is_port, is_parameter, is_output); // Check for specially handled types const Iterator t = properties.find(_uris.rdf_type); @@ -337,6 +338,10 @@ ClientStore::put(const Raul::URI& uri, SPtr p(new PortModel(uris(), path, index, pdir)); p->set_properties(properties); add_object(p); + } else if (is_parameter) { + SPtr p(new ParameterModel(uris(), path)); + p->set_properties(properties); + add_object(p); } else { _log.warn(fmt("Ignoring %1% of unknown type\n") % path.c_str()); } diff --git a/src/gui/NodeModule.cpp b/src/gui/NodeModule.cpp index 746083fb..5914fd2e 100644 --- a/src/gui/NodeModule.cpp +++ b/src/gui/NodeModule.cpp @@ -35,6 +35,7 @@ #include "GraphWindow.hpp" #include "NodeMenu.hpp" #include "NodeModule.hpp" +#include "Parameter.hpp" #include "Port.hpp" #include "RenameWindow.hpp" #include "Style.hpp" @@ -60,6 +61,8 @@ NodeModule::NodeModule(GraphCanvas& canvas, { block->signal_new_port().connect( sigc::mem_fun(this, &NodeModule::new_port_view)); + block->signal_new_parameter().connect( + sigc::mem_fun(this, &NodeModule::new_parameter_view)); block->signal_removed_port().connect( sigc::hide_return(sigc::mem_fun(this, &NodeModule::delete_port_view))); block->signal_property().connect( @@ -124,6 +127,9 @@ NodeModule::create(GraphCanvas& canvas, for (const auto& p : block->ports()) ret->new_port_view(p); + for (const auto& p : block->parameters()) + ret->new_parameter_view(p); + ret->set_stacked(block->polyphonic()); if (human) @@ -205,8 +211,12 @@ NodeModule::port_value_changed(uint32_t index, const Atom& value) void NodeModule::plugin_changed() { - for (iterator p = begin(); p != end(); ++p) - dynamic_cast(*p)->update_metadata(); + for (iterator p = begin(); p != end(); ++p) { + Ingen::GUI::Port* port = dynamic_cast(*p); + if (port) { + port->update_metadata(); + } + } } void @@ -308,6 +318,22 @@ NodeModule::delete_port_view(SPtr model) } } +void +NodeModule::new_parameter_view(SPtr parameter) +{ + fprintf(stderr, "New parameter!\n"); + + Parameter::create(app(), *this, parameter); + + // parameter->signal_value_changed().connect( + // sigc::bind<0>(sigc::mem_fun(this, &NodeModule::parameter_value_changed), + // parameter->index())); + + // parameter->signal_activity().connect( + // sigc::bind<0>(sigc::mem_fun(this, &NodeModule::parameter_activity), + // parameter->index())); +} + bool NodeModule::popup_gui() { diff --git a/src/gui/NodeModule.hpp b/src/gui/NodeModule.hpp index 2d9a3333..262a478b 100644 --- a/src/gui/NodeModule.hpp +++ b/src/gui/NodeModule.hpp @@ -26,6 +26,7 @@ namespace Raul { class Atom; } namespace Ingen { namespace Client { class BlockModel; +class ParameterModel; class PluginUI; class PortModel; } } @@ -81,6 +82,7 @@ protected: void property_changed(const Raul::URI& predicate, const Atom& value); void new_port_view(SPtr port); + void new_parameter_view(SPtr parameter); void port_activity(uint32_t index, const Atom& value); void port_value_changed(uint32_t index, const Atom& value); diff --git a/src/gui/Parameter.cpp b/src/gui/Parameter.cpp new file mode 100644 index 00000000..464a9e67 --- /dev/null +++ b/src/gui/Parameter.cpp @@ -0,0 +1,520 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard + + 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 . +*/ + +#include +#include + +#include "ganv/Module.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Interface.hpp" +#include "ingen/Log.hpp" +#include "ingen/client/GraphModel.hpp" +#include "ingen/client/ParameterModel.hpp" + +#include "App.hpp" +#include "GraphWindow.hpp" +#include "Parameter.hpp" +// #include "ParameterMenu.hpp" +#include "RDFS.hpp" +#include "Style.hpp" +#include "WidgetFactory.hpp" +#include "WindowFactory.hpp" +#include "ingen_config.h" + +using namespace Ingen::Client; +using namespace std; + +namespace Ingen { +namespace GUI { + +Parameter* +Parameter::create(App& app, + Ganv::Module& module, + SPtr pm, + bool flip) +{ + return new Parameter(app, module, pm, parameter_label(app, pm), flip); +} + +/** @param flip Make an input parameter appear as an output parameter, and vice versa. + */ +Parameter::Parameter(App& app, + Ganv::Module& module, + SPtr pm, + const string& name, + bool flip) + : Ganv::Port(module, name, + true, //flip ? (!pm->is_input()) : pm->is_input(), + 0x8888FF) //app.style()->get_parameter_color(pm.get())) + , _app(app) + , _parameter_model(pm) + , _entered(false) + , _flipped(flip) +{ + assert(pm); + + // if (app.can_control(pm.get())) { + show_control(); + pm->signal_value_changed().connect( + sigc::mem_fun(this, &Parameter::value_changed)); + // } + + parameter_properties_changed(); + + pm->signal_property().connect( + sigc::mem_fun(this, &Parameter::property_changed)); + pm->signal_property_removed().connect( + sigc::mem_fun(this, &Parameter::property_removed)); + pm->signal_moved().connect( + sigc::mem_fun(this, &Parameter::moved)); + + signal_value_changed.connect( + sigc::mem_fun(this, &Parameter::on_value_changed)); + + signal_event().connect( + sigc::mem_fun(this, &Parameter::on_event)); + + // set_is_controllable(pm->is_numeric() && pm->is_input()); + set_is_controllable(true); + + // Ganv::Port::set_beveled(model()->is_a(_app.uris().lv2_ControlParameter) || + // model()->has_property(_app.uris().atom_bufferType, + // _app.uris().atom_Sequence)); + + update_metadata(); + value_changed(pm->value()); +} + +Parameter::~Parameter() +{ + // _app.activity_parameter_destroyed(this); +} + +std::string +Parameter::parameter_label(App& app, SPtr pm) +{ + if (!pm) { + return ""; + } + + std::string label; + if (app.world()->conf().option("port-labels").get()) { + if (app.world()->conf().option("human-names").get()) { + const Atom& name = pm->get_property(app.uris().rdfs_label); + if (name.type() == app.forge().String) { + label = name.ptr(); + } else { + const SPtr parent( + dynamic_ptr_cast(pm->parent())); + // if (parent && parent->plugin_model()) + // label = parent->plugin_model()->parameter_human_name(pm->index()); + } + } else { + label = pm->path().symbol(); + } + } + return label; +} + +void +Parameter::ensure_label() +{ + if (!get_label()) { + set_label(parameter_label(_app, _parameter_model.lock()).c_str()); + } +} + +void +Parameter::update_metadata() +{ + // Get default range from static data + // const URIs& uris = _app.uris(); + // LilvWorld* lworld = _app.world()->lilv_world(); + // LilvNode* min = lilv_world_get(lworld, property, uris.lv2_minimum, NULL); + // LilvNOde* max = lilv_world_get(lworld, property, uris.lv2_maximum, NULL); + + set_control_min(0.0); + set_control_max(1.0); + + // SPtr pm = _parameter_model.lock(); + // if (pm && _app.can_control(pm.get()) && pm->is_numeric()) { + // SPtr parent = dynamic_ptr_cast(pm->parent()); + // if (parent) { + // float min = 0.0f; + // float max = 1.0f; + // parent->parameter_value_range(pm, min, max, _app.sample_rate()); + // set_control_min(min); + // set_control_max(max); + // } + // } +} + +bool +Parameter::show_menu(GdkEventButton* ev) +{ + // ParameterMenu* menu = NULL; + // WidgetFactory::get_widget_derived("object_menu", menu); + // if (!menu) { + // _app.log().error("Failed to load parameter menu widget\n"); + // return false; + // } + + // menu->init(_app, model(), _flipped); + // menu->popup(ev->button, ev->time); + return true; +} + +void +Parameter::moved() +{ + if (_app.world()->conf().option("port-labels").get() && + !_app.world()->conf().option("human-names").get()) { + set_label(model()->symbol().c_str()); + } +} + +void +Parameter::on_value_changed(double value) +{ + const URIs& uris = _app.uris(); + const Atom& current_value = model()->value(); + // if (current_value.type() != uris.forge.Float) { + // fprintf(stderr, "Non-float\n"); + // return; // Non-float, unsupported + // } + + // if (current_value.get() == (float)value) { + // fprintf(stderr, "No change\n"); + // return; // No change + // } + + fprintf(stderr, "Change param\n"); + const Atom atom = _app.forge().make(float(value)); + _app.set_property(model()->uri(), + _app.world()->uris().ingen_value, + atom); + + // if (_entered) { + // GraphBox* box = get_graph_box(); + // if (box) { + // box->show_port_status(model().get(), atom); + // } + // } +} + +void +Parameter::value_changed(const Atom& value) +{ + if (value.type() == _app.forge().Float && !get_grabbed()) { + Ganv::Port::set_control_value(value.get()); + } +} + +void +Parameter::on_scale_point_activated(float f) +{ + _app.set_property(model()->uri(), + _app.world()->uris().ingen_value, + _app.world()->forge().make(f)); +} + +Gtk::Menu* +Parameter::build_enum_menu() +{ + SPtr block = dynamic_ptr_cast(model()->parent()); + Gtk::Menu* menu = Gtk::manage(new Gtk::Menu()); + +#if 0 + PluginModel::ScalePoints points = block->plugin_model()->parameter_scale_points( + model()->index()); + for (PluginModel::ScalePoints::iterator i = points.begin(); + i != points.end(); ++i) { + menu->items().push_back(Gtk::Menu_Helpers::MenuElem(i->second)); + Gtk::MenuItem* menu_item = &(menu->items().back()); + menu_item->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Parameter::on_scale_point_activated), + i->first)); + } + +#endif + return menu; +} + +void +Parameter::on_uri_activated(const Raul::URI& uri) +{ + _app.set_property(model()->uri(), + _app.world()->uris().ingen_value, + _app.world()->forge().make_urid( + _app.world()->uri_map().map_uri(uri.c_str()))); +} + +Gtk::Menu* +Parameter::build_uri_menu() +{ + World* world = _app.world(); + SPtr block = dynamic_ptr_cast(model()->parent()); + Gtk::Menu* menu = Gtk::manage(new Gtk::Menu()); + + // Get the parameter designation, which should be a rdf:Property + const Atom& designation_atom = model()->get_property( + _app.uris().lv2_designation); + if (!designation_atom.is_valid()) { + return NULL; + } + + LilvNode* designation = lilv_new_uri( + world->lilv_world(), world->forge().str(designation_atom, false).c_str()); + LilvNode* rdfs_range = lilv_new_uri( + world->lilv_world(), LILV_NS_RDFS "range"); + + // Get every class in the range of the parameter's property + RDFS::URISet ranges; + LilvNodes* range = lilv_world_find_nodes( + world->lilv_world(), designation, rdfs_range, NULL); + LILV_FOREACH(nodes, r, range) { + ranges.insert(Raul::URI(lilv_node_as_string(lilv_nodes_get(range, r)))); + } + RDFS::classes(world, ranges, false); + + // Get all objects in range + RDFS::Objects values = RDFS::instances(world, ranges); + + // Add a menu item for each such class + for (const auto& v : values) { + if (!v.first.empty()) { + const std::string qname = world->rdf_world()->prefixes().qualify(v.second); + const std::string label = qname + " - " + v.first; + menu->items().push_back(Gtk::Menu_Helpers::MenuElem(label)); + Gtk::MenuItem* menu_item = &(menu->items().back()); + menu_item->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Parameter::on_uri_activated), + v.second)); + } + } + + return menu; +} + +bool +Parameter::on_event(GdkEvent* ev) +{ + GraphBox* box = NULL; + switch (ev->type) { + case GDK_ENTER_NOTIFY: + _entered = true; + if ((box = get_graph_box())) { + box->object_entered(model().get()); + } + return false; + case GDK_LEAVE_NOTIFY: + _entered = false; + if ((box = get_graph_box())) { + box->object_left(model().get()); + } + return false; + case GDK_BUTTON_PRESS: + if (ev->button.button == 1) { + // if (model()->is_enumeration()) { + // Gtk::Menu* menu = build_enum_menu(); + // menu->popup(ev->button.button, ev->button.time); + // return true; + // } else if (model()->is_uri()) { + // Gtk::Menu* menu = build_uri_menu(); + // if (menu) { + // menu->popup(ev->button.button, ev->button.time); + // return true; + // } + // } + } else if (ev->button.button == 3) { + return show_menu(&ev->button); + } + break; + default: + break; + } + + return false; +} + +GraphBox* +Parameter::get_graph_box() const +{ + SPtr graph = dynamic_ptr_cast(model()->parent()); + if (!graph) { + graph = dynamic_ptr_cast(model()->parent()->parent()); + } + + return _app.window_factory()->graph_box(graph); +} + +void +Parameter::set_type_tag() +{ +#if 0 + const URIs& uris = _app.uris(); + std::string tag; + if (model()->is_a(_app.uris().lv2_AudioParameter)) { + tag = "~"; + } else if (model()->is_a(_app.uris().lv2_CVParameter)) { + tag = "ℝ̰"; + } else if (model()->is_a(_app.uris().lv2_ControlParameter)) { + if (model()->is_enumeration()) { + tag = "…"; + } else if (model()->is_integer()) { + tag = "ℤ"; + } else if (model()->is_toggle()) { + tag = ((model()->value() != _app.uris().forge.make(0.0f)) + ? "☑" : "☐"); + + } else { + tag = "ℝ"; + } + } else if (model()->is_a(_app.uris().atom_AtomParameter)) { + if (model()->supports(_app.uris().atom_Float)) { + if (model()->is_toggle()) { + tag = ((model()->value() != _app.uris().forge.make(0.0f)) + ? "☑" : "☐"); + } else { + tag = "ℝ"; + } + } + if (model()->supports(_app.uris().atom_Int)) { + tag += "ℤ"; + } + if (model()->supports(_app.uris().midi_MidiEvent)) { + tag += "𝕄"; + } + if (model()->supports(_app.uris().patch_Message)) { + if (tag.empty()) { + tag += "="; + } else { + tag += "̿"; + } + } + if (tag.empty()) { + tag = "*"; + } + + if (model()->has_property(uris.atom_bufferType, uris.atom_Sequence)) { + tag += "̤"; + } + } + + if (!tag.empty()) { + set_value_label(tag.c_str()); + } +#endif +} + +void +Parameter::parameter_properties_changed() +{ + // if (model()->is_toggle()) { + // set_control_is_toggle(true); + // } else if (model()->is_integer()) { + // set_control_is_integer(true); + // } + // set_type_tag(); +} + +void +Parameter::property_changed(const Raul::URI& key, const Atom& value) +{ +#if 0 + const URIs& uris = _app.uris(); + if (value.type() == uris.forge.Float) { + float val = value.get(); + if (key == uris.ingen_value && !get_grabbed()) { + Ganv::Port::set_control_value(val); + if (model()->is_toggle()) { + std::string tag = (val == 0.0f) ? "☐" : "☑"; + if (model()->is_a(_app.uris().lv2_CVParameter)) { + tag += "̰"; + } else if (model()->has_property(uris.atom_bufferType, + uris.atom_Sequence)) { + tag += "̤"; + } + set_value_label(tag.c_str()); + } + } else if (key == uris.lv2_minimum) { + if (model()->parameter_property(uris.lv2_sampleRate)) { + val *= _app.sample_rate(); + } + set_control_min(val); + } else if (key == uris.lv2_maximum) { + if (model()->parameter_property(uris.lv2_sampleRate)) { + val *= _app.sample_rate(); + } + set_control_max(val); + } + } else if (key == uris.lv2_parameterProperty) { + parameter_properties_changed(); + } else if (key == uris.lv2_name) { + if (value.type() == uris.forge.String && + _app.world()->conf().option("parameter-labels").get() && + _app.world()->conf().option("human-names").get()) { + set_label(value.ptr()); + } + } else if (key == uris.rdf_type || key == uris.atom_bufferType) { + Ganv::Port::set_beveled(model()->is_a(uris.lv2_ControlParameter) || + model()->has_property(uris.atom_bufferType, + uris.atom_Sequence)); + } +#endif +} + +void +Parameter::property_removed(const Raul::URI& key, const Atom& value) +{ + const URIs& uris = _app.uris(); + if (key == uris.lv2_minimum || key == uris.lv2_maximum) { + update_metadata(); +#if 0 + } else if (key == uris.rdf_type || key == uris.atom_bufferType) { + Ganv::Port::set_beveled(model()->is_a(uris.lv2_ControlParameter) || + model()->has_property(uris.atom_bufferType, + uris.atom_Sequence)); +#endif + } +} + +bool +Parameter::on_selected(gboolean b) +{ +#if 0 + if (b) { + SPtr pm = _parameter_model.lock(); + if (pm) { + SPtr block = dynamic_ptr_cast(pm->parent()); + GraphWindow* win = _app.window_factory()->parent_graph_window(block); + if (win && win->documentation_is_visible() && block->plugin_model()) { + bool html = false; +#ifdef HAVE_WEBKIT + html = true; +#endif + const std::string& doc = block->plugin_model()->parameter_documentation( + pm->index(), html); + win->set_documentation(doc, html); + } + } + } +#endif + return true; +} + +} // namespace GUI +} // namespace Ingen diff --git a/src/gui/Parameter.hpp b/src/gui/Parameter.hpp new file mode 100644 index 00000000..c1d6bfb0 --- /dev/null +++ b/src/gui/Parameter.hpp @@ -0,0 +1,102 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard + + 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 . +*/ + +#ifndef INGEN_GUI_PARAMETER_HPP +#define INGEN_GUI_PARAMETER_HPP + +#include +#include + +#include + +#include "ganv/Port.hpp" +#include "ingen/types.hpp" + +namespace Raul { +class Atom; +class URI; +} + +namespace Ingen { + +namespace Client { class ParameterModel; } + +namespace GUI { + +class App; +class GraphBox; + +/** A Parameter on an Module. + * + * \ingroup GUI + */ +class Parameter : public Ganv::Port +{ +public: + static Parameter* create( + App& app, + Ganv::Module& module, + SPtr pm, + bool flip = false); + + ~Parameter(); + + SPtr model() const { return _parameter_model.lock(); } + + bool show_menu(GdkEventButton* ev); + void update_metadata(); + void ensure_label(); + + void value_changed(const Atom& value); + void activity(const Atom& value); + void disconnected_from(SPtr parameter); + + bool on_selected(gboolean b); + +private: + Parameter(App& app, + Ganv::Module& module, + SPtr pm, + const std::string& name, + bool flip = false); + + static std::string parameter_label(App& app, SPtr pm); + + Gtk::Menu* build_enum_menu(); + Gtk::Menu* build_uri_menu(); + GraphBox* get_graph_box() const; + + void property_changed(const Raul::URI& key, const Atom& value); + void property_removed(const Raul::URI& key, const Atom& value); + void moved(); + + void on_value_changed(double value); + void on_scale_point_activated(float f); + void on_uri_activated(const Raul::URI& uri); + bool on_event(GdkEvent* ev); + void parameter_properties_changed(); + void set_type_tag(); + + App& _app; + WPtr _parameter_model; + bool _entered : 1; + bool _flipped : 1; +}; + +} // namespace GUI +} // namespace Ingen + +#endif // INGEN_GUI_PARAMETER_HPP diff --git a/src/gui/wscript b/src/gui/wscript index 1beba44b..79b0709b 100644 --- a/src/gui/wscript +++ b/src/gui/wscript @@ -66,6 +66,7 @@ def build(bld): NodeModule.cpp ObjectMenu.cpp PluginMenu.cpp + Parameter.cpp Port.cpp PortMenu.cpp PropertiesWindow.cpp diff --git a/src/server/BlockImpl.hpp b/src/server/BlockImpl.hpp index 47eaa6eb..b2816f7d 100644 --- a/src/server/BlockImpl.hpp +++ b/src/server/BlockImpl.hpp @@ -28,6 +28,7 @@ #include "BufferRef.hpp" #include "NodeImpl.hpp" +#include "Parameter.hpp" #include "PluginImpl.hpp" #include "PortType.hpp" #include "RunContext.hpp" @@ -134,6 +135,10 @@ public: virtual Node* port(uint32_t index) const; virtual PortImpl* port_impl(uint32_t index) const { return (*_ports)[index]; } + typedef std::list Parameters; + Parameters& parameters() { return _parameters; } + const Parameters& parameters() const { return _parameters; } + /** Get a port by symbol. */ virtual PortImpl* port_by_symbol(const char* symbol); @@ -187,6 +192,7 @@ protected: PluginImpl* _plugin; Raul::Array* _ports; ///< Access in audio thread only + Parameters _parameters; uint32_t _polyphony; std::set _providers; ///< Blocks connected to this one's input ports std::set _dependants; ///< Blocks this one's output ports are connected to diff --git a/src/server/ClientUpdate.cpp b/src/server/ClientUpdate.cpp index 217d3a32..673c6f9e 100644 --- a/src/server/ClientUpdate.cpp +++ b/src/server/ClientUpdate.cpp @@ -49,6 +49,13 @@ ClientUpdate::put_port(const PortImpl* port) } } +void +ClientUpdate::put_parameter(const Parameter* parameter) +{ + fprintf(stderr, "Put parameter\n"); + put(parameter->uri(), parameter->properties()); +} + void ClientUpdate::put_block(const BlockImpl* block) { @@ -62,6 +69,9 @@ ClientUpdate::put_block(const BlockImpl* block) for (size_t j = 0; j < block->num_ports(); ++j) { put_port(block->port_impl(j)); } + for (const Parameter& p : block->parameters()) { + put_parameter(&p); + } } } @@ -86,6 +96,11 @@ ClientUpdate::put_graph(const GraphImpl* graph) put_port(graph->port_impl(i)); } + // Enqueue parameters + for (const Parameter& p : graph->parameters()) { + put_parameter(&p); + } + // Enqueue arcs for (const auto& a : graph->arcs()) { const SPtr arc = a.second; diff --git a/src/server/ClientUpdate.hpp b/src/server/ClientUpdate.hpp index dcdcc132..c4181daa 100644 --- a/src/server/ClientUpdate.hpp +++ b/src/server/ClientUpdate.hpp @@ -31,10 +31,11 @@ class URIs; namespace Server { -class PortImpl; class BlockImpl; class GraphImpl; +class Parameter; class PluginImpl; +class PortImpl; /** A sequence of puts/connects/deletes to update clients. * @@ -47,6 +48,7 @@ struct ClientUpdate { Resource::Graph ctx=Resource::Graph::DEFAULT); void put_port(const PortImpl* port); + void put_parameter(const Parameter* parameter); void put_block(const BlockImpl* block); void put_graph(const GraphImpl* graph); void put_plugin(PluginImpl* plugin); diff --git a/src/server/LV2Block.cpp b/src/server/LV2Block.cpp index 7dcfb362..6cb5c1f3 100644 --- a/src/server/LV2Block.cpp +++ b/src/server/LV2Block.cpp @@ -23,8 +23,9 @@ #include #include "lv2/lv2plug.in/ns/ext/morph/morph.h" -#include "lv2/lv2plug.in/ns/ext/presets/presets.h" #include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" #include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" @@ -43,6 +44,7 @@ #include "LV2Block.hpp" #include "LV2Plugin.hpp" #include "OutputPort.hpp" +#include "Parameter.hpp" #include "RunContext.hpp" #include "Worker.hpp" @@ -428,6 +430,22 @@ LV2Block::instantiate(BufferFactory& bufs) lilv_node_free(lv2_connectionOptional); + // Discover properties + bool writable = true; + LilvNodes* properties = lilv_world_find_nodes( + world->lilv_world(), + lilv_plugin_get_uri(plug), + writable ? uris.patch_writable : uris.patch_readable, + NULL); + LILV_FOREACH(nodes, p, properties) { + const LilvNode* property = lilv_nodes_get(properties, p); + _parameters.push_back( + Parameter(world, uris, this, + Raul::URI(lilv_node_as_string(property)), + Raul::Symbol(lilv_node_as_string(lilv_world_get_symbol(world->lilv_world(), property))), + Atom())); + } + if (!ret) { delete _ports; _ports = NULL; diff --git a/src/server/Parameter.hpp b/src/server/Parameter.hpp new file mode 100644 index 00000000..d7c0e42a --- /dev/null +++ b/src/server/Parameter.hpp @@ -0,0 +1,82 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + 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 . +*/ + +#ifndef INGEN_ENGINE_PARAMETER_HPP +#define INGEN_ENGINE_PARAMETER_HPP + +#include "ingen/World.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; + +/** A parameter (message-based control) on a Block. + * + * \ingroup engine + */ +class Parameter : public NodeImpl +{ +public: + static Atom node_to_atom(Forge& forge, const LilvNode* node) { + if (lilv_node_is_float(node)) { + return forge.make(lilv_node_as_float(node)); + } else if (lilv_node_is_uri(node)) { + return forge.make_urid(Raul::URI(lilv_node_as_uri(node))); + } + return Atom(); + } + + Parameter(Ingen::World* world, + const Ingen::URIs& uris, + NodeImpl* parent, + const Raul::URI& uri, + const Raul::Symbol& symbol, + const Atom& value) + : NodeImpl(uris, parent, symbol) + , _value(value) + { + set_property(uris.rdf_type, uris.ingen_Parameter); + set_property(uris.lv2_symbol, uris.forge.alloc(symbol)); + LilvWorld* lworld = world->lilv_world(); + LilvNode* prop = lilv_new_uri(lworld, uri.c_str()); + LilvNode* range = lilv_world_get(lworld, prop, uris.rdfs_range, NULL); + set_property(uris.rdfs_range, node_to_atom(world->forge(), range)); + LilvNode* min = lilv_world_get(lworld, prop, uris.lv2_minimum, NULL); + LilvNode* max = lilv_world_get(lworld, prop, uris.lv2_maximum, NULL); + LilvNode* def = lilv_world_get(lworld, prop, uris.lv2_default, NULL); + set_property(uris.ingen_value, node_to_atom(world->forge(), def)); + set_property(uris.lv2_minimum, node_to_atom(world->forge(), min)); + set_property(uris.lv2_maximum, node_to_atom(world->forge(), max)); + } + + virtual GraphType graph_type() const { return GraphType::PARAMETER; } + + /** A parameter's parent is always a block. */ + BlockImpl* parent_block() const { return (BlockImpl*)_parent; } + + // n/a + virtual bool prepare_poly(BufferFactory&, uint32_t) { return false; } + virtual bool apply_poly(RunContext&, Raul::Maid&, uint32_t) { return false; } + +protected: + Atom _value; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PARAMETER_HPP diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp index 49ea27ff..06269f3a 100644 --- a/src/server/events/Delta.cpp +++ b/src/server/events/Delta.cpp @@ -216,8 +216,8 @@ Delta::pre_process() if (is_graph_object && !_object) { Raul::Path path(Node::uri_to_path(_subject)); - bool is_graph = false, is_block = false, is_port = false, is_output = false; - Ingen::Resource::type(uris, _properties, is_graph, is_block, is_port, is_output); + bool is_graph = false, is_block = false, is_port = false, is_parameter = false, is_output = false; + Ingen::Resource::type(uris, _properties, is_graph, is_block, is_port, is_parameter, is_output); if (is_graph) { _create_event = new CreateGraph( -- cgit v1.2.1