summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2010-02-04 01:29:34 +0000
committerDavid Robillard <d@drobilla.net>2010-02-04 01:29:34 +0000
commit36dc6bdb6d9b96e2a18acbe6c681a842d1590fb6 (patch)
treeeb01898024d18a0d1463a03cbcfaeb0a7e3956c4
parentaef3c9348218029a077f40081fd078a9e2ee85ca (diff)
downloadingen-36dc6bdb6d9b96e2a18acbe6c681a842d1590fb6.tar.gz
ingen-36dc6bdb6d9b96e2a18acbe6c681a842d1590fb6.tar.bz2
ingen-36dc6bdb6d9b96e2a18acbe6c681a842d1590fb6.zip
MIDI feedback.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@2418 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--src/engine/ControlBindings.cpp306
-rw-r--r--src/engine/ControlBindings.hpp33
-rw-r--r--src/engine/Engine.cpp40
-rw-r--r--src/engine/EventBuffer.cpp4
-rw-r--r--src/engine/JackDriver.cpp16
-rw-r--r--src/engine/events/CreatePort.cpp2
-rw-r--r--src/engine/events/SetMetadata.cpp2
-rw-r--r--src/engine/events/SetPortValue.cpp5
8 files changed, 273 insertions, 135 deletions
diff --git a/src/engine/ControlBindings.cpp b/src/engine/ControlBindings.cpp
index 6fb1c6a8..7f58c057 100644
--- a/src/engine/ControlBindings.cpp
+++ b/src/engine/ControlBindings.cpp
@@ -15,8 +15,10 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <math.h>
#include "raul/log.hpp"
#include "raul/midi_events.h"
+#include "raul/IntrusivePtr.hpp"
#include "events/SendPortValue.hpp"
#include "events/SendBinding.hpp"
#include "ControlBindings.hpp"
@@ -34,32 +36,122 @@ using namespace Raul;
namespace Ingen {
-void
-ControlBindings::update_port(ProcessContext& context, PortImpl* port)
+ControlBindings::ControlBindings(Engine& engine, SharedPtr<Shared::LV2URIMap> map)
+ : _engine(engine)
+ , _map(map)
+ , _learn_port(NULL)
+ , _bindings(new Bindings())
+ , _feedback(new EventBuffer(*_engine.buffer_factory(), 1024)) // FIXME: size
{
- const Shared::LV2URIMap& uris = Shared::LV2URIMap::instance();
- const Raul::Atom& binding = port->get_property(uris.ingen_controlBinding);
+}
+
+
+ControlBindings::~ControlBindings()
+{
+ delete _feedback;
+}
+
+
+ControlBindings::Key
+ControlBindings::port_binding(PortImpl* port)
+{
+ const Shared::LV2URIMap& uris = Shared::LV2URIMap::instance();
+ const Raul::Atom& binding = port->get_property(uris.ingen_controlBinding);
+ Key key;
if (binding.type() == Atom::DICT) {
- Atom::DictValue::const_iterator t = binding.get_dict().find(uris.rdf_type);
+ const Atom::DictValue& dict = binding.get_dict();
+ Atom::DictValue::const_iterator t = dict.find(uris.rdf_type);
Atom::DictValue::const_iterator n;
- if (t != binding.get_dict().end()) {
- if (t->second == uris.midi_Bender) {
- _bindings->insert(make_pair(Key(MIDI_BENDER), port));
- } else if (t->second == uris.midi_Bender) {
- _bindings->insert(make_pair(Key(MIDI_BENDER), port));
- } else if (t->second == uris.midi_ChannelPressure) {
- _bindings->insert(make_pair(Key(MIDI_CHANNEL_PRESSURE), port));
- } else if (t->second == uris.midi_Controller) {
- n = binding.get_dict().find(uris.midi_controllerNumber);
- if (n != binding.get_dict().end())
- _bindings->insert(make_pair(Key(MIDI_CC, n->second.get_int32()), port));
- } else if (t->second == uris.midi_Note) {
- n = binding.get_dict().find(uris.midi_noteNumber);
- if (n != binding.get_dict().end())
- _bindings->insert(make_pair(Key(MIDI_NOTE, n->second.get_int32()), port));
- }
+ if (t == dict.end()) {
+ return key;
+ } else if (t->second == uris.midi_Bender) {
+ key = Key(MIDI_BENDER);
+ } else if (t->second == uris.midi_ChannelPressure) {
+ key = Key(MIDI_CHANNEL_PRESSURE);
+ } else if (t->second == uris.midi_Controller) {
+ if ((n = dict.find(uris.midi_controllerNumber)) != dict.end())
+ key = Key(MIDI_CC, n->second.get_int32());
+ } else if (t->second == uris.midi_Note) {
+ if ((n = dict.find(uris.midi_noteNumber)) != dict.end())
+ key = Key(MIDI_NOTE, n->second.get_int32());
}
}
+ return key;
+}
+
+
+ControlBindings::Key
+ControlBindings::midi_event_key(uint16_t size, uint8_t* buf, uint16_t& value)
+{
+ switch (buf[0] & 0xF0) {
+ case MIDI_CMD_CONTROL:
+ value = static_cast<const int8_t>(buf[2]);
+ return Key(MIDI_CC, static_cast<const int8_t>(buf[1]));
+ case MIDI_CMD_BENDER:
+ value = (static_cast<int8_t>(buf[2]) << 7) + static_cast<int8_t>(buf[1]);
+ return Key(MIDI_BENDER);
+ case MIDI_CMD_CHANNEL_PRESSURE:
+ value = static_cast<const int8_t>(buf[1]);
+ return Key(MIDI_CHANNEL_PRESSURE);
+ case MIDI_CMD_NOTE_ON:
+ value = 1.0f;
+ return Key(MIDI_NOTE, static_cast<const int8_t>(buf[1]));
+ default:
+ return Key();
+ }
+}
+
+
+void
+ControlBindings::port_binding_changed(ProcessContext& context, PortImpl* port)
+{
+ Key key = port_binding(port);
+ if (key)
+ _bindings->insert(make_pair(key, port));
+}
+
+
+void
+ControlBindings::port_value_changed(ProcessContext& context, PortImpl* port)
+{
+ Key key = port_binding(port);
+ if (key) {
+ int16_t value = port_value_to_control(port, key.type);
+ uint16_t size = 0;
+ uint8_t buf[4];
+ switch (key.type) {
+ case MIDI_CC:
+ size = 3;
+ buf[0] = MIDI_CMD_CONTROL;
+ buf[1] = key.num;
+ buf[2] = static_cast<int8_t>(value);
+ break;
+ case MIDI_CHANNEL_PRESSURE:
+ size = 2;
+ buf[0] = MIDI_CMD_CHANNEL_PRESSURE;
+ buf[1] = static_cast<int8_t>(value);
+ break;
+ case MIDI_BENDER:
+ size = 3;
+ buf[0] = MIDI_CMD_BENDER;
+ buf[1] = (value & 0x007F);
+ buf[2] = (value & 0x7F00) >> 7;
+ break;
+ case MIDI_NOTE:
+ size = 3;
+ if (value == 1)
+ buf[0] = MIDI_CMD_NOTE_ON;
+ else if (value == 0)
+ buf[0] = MIDI_CMD_NOTE_OFF;
+ buf[1] = key.num;
+ buf[2] = 0x64; // MIDI spec default
+ break;
+ default:
+ break;
+ }
+ if (size > 0)
+ _feedback->append(0, 0, _map->midi_event.id, size, buf);
+ }
}
@@ -71,14 +163,15 @@ ControlBindings::learn(PortImpl* port)
}
-void
-ControlBindings::set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value)
+Raul::Atom
+ControlBindings::control_to_port_value(PortImpl* port, Type type, int16_t value)
{
- // TODO: cache these to avoid the lookup
const Shared::LV2URIMap& uris = Shared::LV2URIMap::instance();
- float min = port->get_property(uris.lv2_minimum).get_float();
- float max = port->get_property(uris.lv2_maximum).get_float();
- bool toggled = port->has_property(uris.lv2_portProperty, uris.lv2_toggled);
+
+ // TODO: cache these to avoid the lookup
+ float min = port->get_property(uris.lv2_minimum).get_float();
+ float max = port->get_property(uris.lv2_maximum).get_float();
+ bool toggled = port->has_property(uris.lv2_portProperty, uris.lv2_toggled);
float normal;
switch (type) {
@@ -100,29 +193,63 @@ ControlBindings::set_port_value(ProcessContext& context, PortImpl* port, Type ty
if (toggled)
scaled_value = (scaled_value < 0.5) ? 0.0 : 1.0;
- Raul::Atom atom(scaled_value);
- port->set_value(atom);
+ return Raul::Atom(scaled_value);
+}
+
+
+int16_t
+ControlBindings::port_value_to_control(PortImpl* port, Type type)
+{
+ if (port->value().type() != Atom::FLOAT)
+ return 0;
+
+ const Shared::LV2URIMap& uris = Shared::LV2URIMap::instance();
+
+ // TODO: cache these to avoid the lookup
+ float min = port->get_property(uris.lv2_minimum).get_float();
+ float max = port->get_property(uris.lv2_maximum).get_float();
+ //bool toggled = port->has_property(uris.lv2_portProperty, uris.lv2_toggled);
+ float value = port->value().get_float();
+ float normal = (value - min) / (max - min);
+
+ assert(normal >= 0.0f && normal <= 1.0f);
+ switch (type) {
+ case MIDI_CC:
+ case MIDI_CHANNEL_PRESSURE:
+ return lrintf(normal * 127.0f);
+ case MIDI_BENDER:
+ return lrintf(normal * 16383.0f);
+ case MIDI_NOTE:
+ return (value > 0.0f) ? 1 : 0;
+ default:
+ return 0;
+ }
+}
+
+void
+ControlBindings::set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value)
+{
const Events::SendPortValue ev(context.engine(), context.start(), port, true, 0,
- atom.get_float());
+ control_to_port_value(port, type, value));
context.event_sink().write(sizeof(ev), &ev);
}
bool
-ControlBindings::bind(ProcessContext& context, Type type, int16_t num)
+ControlBindings::bind(ProcessContext& context, Key key)
{
const Shared::LV2URIMap& uris = Shared::LV2URIMap::instance();
assert(_learn_port);
- if (type == MIDI_NOTE) {
+ if (key.type == MIDI_NOTE) {
bool toggled = _learn_port->has_property(uris.lv2_portProperty, uris.lv2_toggled);
if (!toggled)
return false;
}
- _bindings->insert(make_pair(Key(type, num), _learn_port));
+ _bindings->insert(make_pair(key, _learn_port));
- const Events::SendBinding ev(context.engine(), context.start(), _learn_port, type, num);
+ const Events::SendBinding ev(context.engine(), context.start(), _learn_port, key.type, key.num);
context.event_sink().write(sizeof(ev), &ev);
_learn_port = NULL;
@@ -154,88 +281,65 @@ ControlBindings::remove(const Raul::Path& path)
void
-ControlBindings::process(ProcessContext& context, EventBuffer* buffer)
+ControlBindings::pre_process(ProcessContext& context, EventBuffer* buffer)
{
uint32_t frames = 0;
uint32_t subframes = 0;
uint16_t type = 0;
uint16_t size = 0;
uint8_t* buf = NULL;
+ uint16_t value = 0;
SharedPtr<Bindings> bindings = _bindings;
+ _feedback->clear();
+ // Learn from input if necessary
if (_learn_port) {
- buffer->rewind();
- int16_t num;
- while (buffer->get_event(&frames, &subframes, &type, &size, &buf)) {
- if (type == _map->midi_event.id) {
- switch (buf[0] & 0xF0) {
- case MIDI_CMD_CONTROL:
- num = static_cast<const int8_t>(buf[1]);
- bind(context, MIDI_CC, num);
- break;
- case MIDI_CMD_BENDER:
- bind(context, MIDI_BENDER);
- break;
- case MIDI_CMD_CHANNEL_PRESSURE:
- bind(context, MIDI_CHANNEL_PRESSURE);
- break;
- case MIDI_CMD_NOTE_ON:
- num = static_cast<const int8_t>(buf[1]);
- bind(context, MIDI_NOTE, num);
- break;
- default:
- break;
- }
- }
- if (!_learn_port)
+ for (buffer->rewind();
+ buffer->get_event(&frames, &subframes, &type, &size, &buf);
+ buffer->increment()) {
+ if (type != _map->midi_event.id)
+ continue;
+
+ const Key key = midi_event_key(size, buf, value);
+ if (key && bind(context, key))
break;
- buffer->increment();
}
}
- if (!bindings->empty()) {
- buffer->rewind();
- while (buffer->get_event(&frames, &subframes, &type, &size, &buf)) {
- if (type == _map->midi_event.id) {
- if ((buf[0] & 0xF0) == MIDI_CMD_CONTROL) {
- const int8_t controller = static_cast<const int8_t>(buf[1]);
- Bindings::const_iterator i = bindings->find(Key(MIDI_CC, controller));
- if (i != bindings->end()) {
- const int8_t value = static_cast<const int8_t>(buf[2]);
- set_port_value(context, i->second, MIDI_CC, value);
- }
- } else if ((buf[0] & 0xF0) == MIDI_CMD_BENDER) {
- Bindings::const_iterator i = bindings->find(Key(MIDI_BENDER));
- if (i != bindings->end()) {
- const int8_t lsb = static_cast<const int8_t>(buf[1]);
- const int8_t msb = static_cast<const int8_t>(buf[2]);
- const int16_t value = (msb << 7) + lsb;
- set_port_value(context, i->second, MIDI_BENDER, value);
- }
- } else if ((buf[0] & 0xF0) == MIDI_CMD_CHANNEL_PRESSURE) {
- Bindings::const_iterator i = bindings->find(Key(MIDI_CHANNEL_PRESSURE));
- if (i != bindings->end()) {
- const int8_t value = static_cast<const int8_t>(buf[1]);
- set_port_value(context, i->second, MIDI_CHANNEL_PRESSURE, value);
- }
- } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) {
- const int8_t num = static_cast<const int8_t>(buf[1]);
- Bindings::const_iterator i = bindings->find(Key(MIDI_NOTE, num));
- if (i != bindings->end()) {
- set_port_value(context, i->second, MIDI_NOTE, 1.0f);
- }
- } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) {
- const int8_t num = static_cast<const int8_t>(buf[1]);
- Bindings::const_iterator i = bindings->find(Key(MIDI_NOTE, num));
- if (i != bindings->end()) {
- set_port_value(context, i->second, MIDI_NOTE, 0.0f);
- }
- }
- }
- buffer->increment();
- }
+ // If bindings are empty, no sense reading input
+ if (bindings->empty())
+ return;
+
+ // Read input and apply control values
+ for (buffer->rewind();
+ buffer->get_event(&frames, &subframes, &type, &size, &buf);
+ buffer->increment()) {
+ if (type != _map->midi_event.id)
+ continue;
+
+ const Key key = midi_event_key(size, buf, value);
+ if (!key)
+ continue;
+
+ Bindings::const_iterator i = bindings->find(key);
+ if (i == bindings->end())
+ continue;
+
+ set_port_value(context, i->second, key.type, value);
}
}
+
+void
+ControlBindings::post_process(ProcessContext& context, EventBuffer* buffer)
+{
+ if (_feedback->event_count() > 0) {
+ // TODO: merge buffer's existing contents (anything send to it in the patch)
+ _feedback->rewind();
+ buffer->copy(context, _feedback);
+ }
}
+
+
+} // namespace Ingen
diff --git a/src/engine/ControlBindings.hpp b/src/engine/ControlBindings.hpp
index 1227df09..458d153a 100644
--- a/src/engine/ControlBindings.hpp
+++ b/src/engine/ControlBindings.hpp
@@ -23,6 +23,7 @@
#include "raul/SharedPtr.hpp"
#include "raul/Path.hpp"
#include "shared/LV2URIMap.hpp"
+#include "BufferFactory.hpp"
namespace Ingen {
@@ -34,6 +35,7 @@ class PortImpl;
class ControlBindings {
public:
enum Type {
+ NULL_CONTROL,
MIDI_BENDER,
MIDI_CC,
MIDI_RPN,
@@ -43,26 +45,26 @@ public:
};
struct Key {
- Key(Type t, int16_t n=0) : type(t), num(n) {}
+ Key(Type t=NULL_CONTROL, int16_t n=0) : type(t), num(n) {}
inline bool operator<(const Key& other) const {
return (type == other.type) ? (num < other.num) : (type < other.type);
}
+ inline operator bool() const { return type != NULL_CONTROL; }
Type type;
int16_t num;
};
typedef std::map<Key, PortImpl*> Bindings;
- ControlBindings(Engine& engine, SharedPtr<Shared::LV2URIMap> map)
- : _engine(engine)
- , _map(map)
- , _learn_port(NULL)
- , _bindings(new Bindings())
- {}
+ ControlBindings(Engine& engine, SharedPtr<Shared::LV2URIMap> map);
+ ~ControlBindings();
void learn(PortImpl* port);
- void update_port(ProcessContext& context, PortImpl* port);
- void process(ProcessContext& context, EventBuffer* buffer);
+
+ void port_binding_changed(ProcessContext& context, PortImpl* port);
+ void port_value_changed(ProcessContext& context, PortImpl* port);
+ void pre_process(ProcessContext& context, EventBuffer* control_in);
+ void post_process(ProcessContext& context, EventBuffer* control_out);
/** Remove all bindings for @a path or children of @a path.
* The caller must safely drop the returned reference in the
@@ -71,14 +73,21 @@ public:
SharedPtr<Bindings> remove(const Raul::Path& path);
private:
+ Key port_binding(PortImpl* port);
+ Key midi_event_key(uint16_t size, uint8_t* buf, uint16_t& value);
+
+ void set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value);
+ bool bind(ProcessContext& context, Key key);
+
+ Raul::Atom control_to_port_value(PortImpl* port, Type type, int16_t value);
+ int16_t port_value_to_control(PortImpl* port, Type type);
+
Engine& _engine;
SharedPtr<Shared::LV2URIMap> _map;
PortImpl* _learn_port;
- void set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value);
- bool bind(ProcessContext& context, Type type, int16_t num=0);
-
SharedPtr<Bindings> _bindings;
+ EventBuffer* _feedback;
};
} // namespace Ingen
diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp
index 78420e3a..3f65b587 100644
--- a/src/engine/Engine.cpp
+++ b/src/engine/Engine.cpp
@@ -151,6 +151,16 @@ Engine::add_event_source(SharedPtr<EventSource> source)
}
+static void
+execute_and_delete_event(ProcessContext& context, QueuedEvent* ev)
+{
+ ev->pre_process();
+ ev->execute(context);
+ ev->post_process();
+ delete ev;
+}
+
+
bool
Engine::activate()
{
@@ -178,18 +188,26 @@ Engine::activate()
root_patch->compiled_patch(root_patch->compile());
_driver->set_root_patch(root_patch);
- // Add control port
- Shared::Resource::Properties properties;
- properties.insert(make_pair(uris.lv2_name, "Control"));
- properties.insert(make_pair(uris.rdf_type, uris.lv2ev_EventPort));
- properties.insert(make_pair(uris.rdf_type, uris.lv2_InputPort));
- Events::CreatePort* ev = new Events::CreatePort(*this, SharedPtr<Request>(), 0,
- "/ingen_control", uris.lv2ev_EventPort, false, properties);
- ev->pre_process();
ProcessContext context(*this);
- ev->execute(context);
- ev->post_process();
- delete ev;
+
+ Shared::Resource::Properties control_properties;
+ control_properties.insert(make_pair(uris.lv2_name, "Control"));
+ control_properties.insert(make_pair(uris.rdf_type, uris.lv2ev_EventPort));
+
+ // Add control input
+ Shared::Resource::Properties in_properties(control_properties);
+ in_properties.insert(make_pair(uris.rdf_type, uris.lv2_InputPort));
+
+ execute_and_delete_event(context, new Events::CreatePort(
+ *this, SharedPtr<Request>(), 0,
+ "/control_in", uris.lv2ev_EventPort, false, in_properties));
+
+ // Add control out
+ Shared::Resource::Properties out_properties(control_properties);
+ out_properties.insert(make_pair(uris.rdf_type, uris.lv2_OutputPort));
+ execute_and_delete_event(context, new Events::CreatePort(
+ *this, SharedPtr<Request>(), 0,
+ "/control_out", uris.lv2ev_EventPort, true, out_properties));
}
_driver->activate();
diff --git a/src/engine/EventBuffer.cpp b/src/engine/EventBuffer.cpp
index 6b27ef4e..e1e55fdb 100644
--- a/src/engine/EventBuffer.cpp
+++ b/src/engine/EventBuffer.cpp
@@ -94,11 +94,11 @@ EventBuffer::copy(Context& context, const Buffer* src_buf)
if (src->_data == _data)
return;
- assert(capacity() >= src->capacity());
+ assert(src->_data->header_size == _data->header_size);
+ assert(capacity() >= _data->header_size + src->_data->size);
rewind();
- assert(src->_data->header_size == _data->header_size);
memcpy(_data, src->_data, _data->header_size + src->_data->size);
_iter = src->_iter;
diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp
index feb8d802..fc2e5e59 100644
--- a/src/engine/JackDriver.cpp
+++ b/src/engine/JackDriver.cpp
@@ -427,22 +427,26 @@ JackDriver::_process_cb(jack_nframes_t nframes)
(*i)->context().set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle);
}
- // Process events that came in during the last cycle
- // (Aiming for jitter-free 1 block event latency, ideally)
- _engine.process_events(_process_context);
-
// Read input
for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i)
(*i)->pre_process(_process_context);
- // Process control bindings
- _engine.control_bindings()->process(_process_context,
+ // Apply control bindings to input
+ _engine.control_bindings()->pre_process(_process_context,
PtrCast<EventBuffer>(_root_patch->port_impl(0)->buffer(0)).get());
+ // Process events that came in during the last cycle
+ // (Aiming for jitter-free 1 block event latency, ideally)
+ _engine.process_events(_process_context);
+
// Run root patch
if (_root_patch)
_root_patch->process(_process_context);
+ // Emit control binding feedback
+ _engine.control_bindings()->post_process(_process_context,
+ PtrCast<EventBuffer>(_root_patch->port_impl(1)->buffer(0)).get());
+
// Signal message context to run if necessary
if (_engine.message_context()->has_requests())
_engine.message_context()->signal(_process_context);
diff --git a/src/engine/events/CreatePort.cpp b/src/engine/events/CreatePort.cpp
index 7b63f6c5..b12f25dc 100644
--- a/src/engine/events/CreatePort.cpp
+++ b/src/engine/events/CreatePort.cpp
@@ -137,7 +137,7 @@ CreatePort::execute(ProcessContext& context)
if (_patch_port) {
_engine.maid()->push(_patch->external_ports());
_patch->external_ports(_ports_array);
- _engine.control_bindings()->update_port(context, _patch_port);
+ _engine.control_bindings()->port_binding_changed(context, _patch_port);
}
if (_driver_port) {
diff --git a/src/engine/events/SetMetadata.cpp b/src/engine/events/SetMetadata.cpp
index 8165f554..9b615a49 100644
--- a/src/engine/events/SetMetadata.cpp
+++ b/src/engine/events/SetMetadata.cpp
@@ -257,7 +257,7 @@ SetMetadata::execute(ProcessContext& context)
break;
case CONTROL_BINDING:
if ((port = dynamic_cast<PortImpl*>(_object)))
- _engine.control_bindings()->update_port(context, port);
+ _engine.control_bindings()->port_binding_changed(context, port);
default:
_success = true;
}
diff --git a/src/engine/events/SetPortValue.cpp b/src/engine/events/SetPortValue.cpp
index aaa387b0..892abb67 100644
--- a/src/engine/events/SetPortValue.cpp
+++ b/src/engine/events/SetPortValue.cpp
@@ -23,8 +23,9 @@
#include "shared/LV2Object.hpp"
#include "module/World.hpp"
#include "AudioBuffer.hpp"
-#include "Driver.hpp"
#include "ClientBroadcaster.hpp"
+#include "ControlBindings.hpp"
+#include "Driver.hpp"
#include "Engine.hpp"
#include "EngineStore.hpp"
#include "EventBuffer.hpp"
@@ -33,6 +34,7 @@
#include "ObjectBuffer.hpp"
#include "PortImpl.hpp"
#include "ProcessContext.hpp"
+#include "ProcessContext.hpp"
#include "Request.hpp"
#include "SetPortValue.hpp"
@@ -143,6 +145,7 @@ SetPortValue::execute(ProcessContext& context)
return;
apply(context);
+ _engine.control_bindings()->port_value_changed(context, _port);
}