summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ingen/Node.hpp3
-rw-r--r--ingen/Resource.hpp1
-rw-r--r--ingen/URIs.hpp4
-rw-r--r--ingen/client/BlockModel.hpp11
-rw-r--r--ingen/client/ParameterModel.hpp63
-rw-r--r--ingen/ingen.h1
-rw-r--r--src/Resource.cpp5
-rw-r--r--src/Store.cpp2
-rw-r--r--src/URIs.cpp4
-rw-r--r--src/client/BlockModel.cpp31
-rw-r--r--src/client/ClientStore.cpp9
-rw-r--r--src/gui/NodeModule.cpp30
-rw-r--r--src/gui/NodeModule.hpp2
-rw-r--r--src/gui/Parameter.cpp520
-rw-r--r--src/gui/Parameter.hpp102
-rw-r--r--src/gui/wscript1
-rw-r--r--src/server/BlockImpl.hpp6
-rw-r--r--src/server/ClientUpdate.cpp15
-rw-r--r--src/server/ClientUpdate.hpp4
-rw-r--r--src/server/LV2Block.cpp20
-rw-r--r--src/server/Parameter.hpp82
-rw-r--r--src/server/events/Delta.cpp4
22 files changed, 904 insertions, 16 deletions
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<const Node*, const Node*> 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<const PortModel> > Ports;
+ typedef std::vector< SPtr<const PortModel> > Ports;
+ typedef std::vector< SPtr<const ParameterModel> > Parameters;
SPtr<const PortModel> get_port(const Raul::Symbol& symbol) const;
SPtr<const PortModel> get_port(uint32_t index) const;
@@ -64,6 +66,7 @@ public:
SPtr<PluginModel> 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<const PortModel> port,
float& min,
@@ -81,6 +84,8 @@ public:
// Signals
INGEN_SIGNAL(new_port, void, SPtr<const PortModel>);
INGEN_SIGNAL(removed_port, void, SPtr<const PortModel>);
+ INGEN_SIGNAL(new_parameter, void, SPtr<const ParameterModel>);
+ INGEN_SIGNAL(removed_parameter, void, SPtr<const ParameterModel>);
protected:
friend class ClientStore;
@@ -98,11 +103,13 @@ protected:
void add_port(SPtr<PortModel> pm);
void remove_port(SPtr<PortModel> pm);
void remove_port(const Raul::Path& port_path);
+ void add_parameter(SPtr<ParameterModel> pm);
void set(SPtr<ObjectModel> 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<PluginModel> _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 <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/>.
+*/
+
+#ifndef INGEN_CLIENT_PARAMETERMODEL_HPP
+#define INGEN_CLIENT_PARAMETERMODEL_HPP
+
+#include <cstdlib>
+#include <string>
+
+#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<ObjectModel> 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 <http://drobilla.net/>
+ Copyright 2007-2016 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
@@ -18,9 +18,10 @@
#include <cmath>
#include <string>
-#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<ObjectModel> c)
//ObjectModel::add_child(c);
SPtr<PortModel> pm = dynamic_ptr_cast<PortModel>(c);
- assert(pm);
- add_port(pm);
+ if (pm) {
+ add_port(pm);
+ return;
+ }
+
+ SPtr<ParameterModel> am = dynamic_ptr_cast<ParameterModel>(c);
+ if (am) {
+ add_parameter(am);
+ return;
+ }
}
bool
@@ -140,6 +149,20 @@ BlockModel::add_port(SPtr<PortModel> pm)
_signal_new_port.emit(pm);
}
+void
+BlockModel::add_parameter(SPtr<ParameterModel> 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<const PortModel>
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<PortModel> p(new PortModel(uris(), path, index, pdir));
p->set_properties(properties);
add_object(p);
+ } else if (is_parameter) {
+ SPtr<ObjectModel> 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<Ingen::GUI::Port*>(*p)->update_metadata();
+ for (iterator p = begin(); p != end(); ++p) {
+ Ingen::GUI::Port* port = dynamic_cast<Ingen::GUI::Port*>(*p);
+ if (port) {
+ port->update_metadata();
+ }
+ }
}
void
@@ -308,6 +318,22 @@ NodeModule::delete_port_view(SPtr<const PortModel> model)
}
}
+void
+NodeModule::new_parameter_view(SPtr<const ParameterModel> 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<const Client::PortModel> port);
+ void new_parameter_view(SPtr<const Client::ParameterModel> 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 <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 <string>
+
+#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<const ParameterModel> 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<const ParameterModel> 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<const ParameterModel> pm)
+{
+ if (!pm) {
+ return "";
+ }
+
+ std::string label;
+ if (app.world()->conf().option("port-labels").get<int32_t>()) {
+ if (app.world()->conf().option("human-names").get<int32_t>()) {
+ const Atom& name = pm->get_property(app.uris().rdfs_label);
+ if (name.type() == app.forge().String) {
+ label = name.ptr<char>();
+ } else {
+ const SPtr<const BlockModel> parent(
+ dynamic_ptr_cast<const BlockModel>(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<const ParameterModel> pm = _parameter_model.lock();
+ // if (pm && _app.can_control(pm.get()) && pm->is_numeric()) {
+ // SPtr<const BlockModel> parent = dynamic_ptr_cast<const BlockModel>(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<int32_t>() &&
+ !_app.world()->conf().option("human-names").get<int32_t>()) {
+ 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>() == (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<float>());
+ }
+}
+
+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<const BlockModel> block = dynamic_ptr_cast<BlockModel>(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<const BlockModel> block = dynamic_ptr_cast<BlockModel>(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<const GraphModel> graph = dynamic_ptr_cast<const GraphModel>(model()->parent());
+ if (!graph) {
+ graph = dynamic_ptr_cast<const GraphModel>(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<float>();
+ 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<int32_t>() &&
+ _app.world()->conf().option("human-names").get<int32_t>()) {
+ set_label(value.ptr<char>());
+ }
+ } 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<const ParameterModel> pm = _parameter_model.lock();
+ if (pm) {
+ SPtr<const BlockModel> block = dynamic_ptr_cast<const BlockModel>(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 <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/>.
+*/
+
+#ifndef INGEN_GUI_PARAMETER_HPP
+#define INGEN_GUI_PARAMETER_HPP
+
+#include <cassert>
+#include <string>
+
+#include <gtkmm/menu.h>
+
+#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<const Client::ParameterModel> pm,
+ bool flip = false);
+
+ ~Parameter();
+
+ SPtr<const Client::ParameterModel> 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<Client::ParameterModel> parameter);
+
+ bool on_selected(gboolean b);
+
+private:
+ Parameter(App& app,
+ Ganv::Module& module,
+ SPtr<const Client::ParameterModel> pm,
+ const std::string& name,
+ bool flip = false);
+
+ static std::string parameter_label(App& app, SPtr<const Client::ParameterModel> 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<const Client::ParameterModel> _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<Parameter> 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<PortImpl*>* _ports; ///< Access in audio thread only
+ Parameters _parameters;
uint32_t _polyphony;
std::set<BlockImpl*> _providers; ///< Blocks connected to this one's input ports
std::set<BlockImpl*> _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
@@ -50,6 +50,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)
{
const PluginImpl* const plugin = block->plugin_impl();
@@ -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<const Arc> 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 <glibmm/convert.h>
#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 <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/>.
+*/
+
+#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(