diff options
author | David Robillard <d@drobilla.net> | 2010-02-04 01:29:34 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2010-02-04 01:29:34 +0000 |
commit | 36dc6bdb6d9b96e2a18acbe6c681a842d1590fb6 (patch) | |
tree | eb01898024d18a0d1463a03cbcfaeb0a7e3956c4 | |
parent | aef3c9348218029a077f40081fd078a9e2ee85ca (diff) | |
download | ingen-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.cpp | 306 | ||||
-rw-r--r-- | src/engine/ControlBindings.hpp | 33 | ||||
-rw-r--r-- | src/engine/Engine.cpp | 40 | ||||
-rw-r--r-- | src/engine/EventBuffer.cpp | 4 | ||||
-rw-r--r-- | src/engine/JackDriver.cpp | 16 | ||||
-rw-r--r-- | src/engine/events/CreatePort.cpp | 2 | ||||
-rw-r--r-- | src/engine/events/SetMetadata.cpp | 2 | ||||
-rw-r--r-- | src/engine/events/SetPortValue.cpp | 5 |
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); } |