diff options
Diffstat (limited to 'src/server/ControlBindings.cpp')
-rw-r--r-- | src/server/ControlBindings.cpp | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/server/ControlBindings.cpp b/src/server/ControlBindings.cpp new file mode 100644 index 00000000..94a12a7e --- /dev/null +++ b/src/server/ControlBindings.cpp @@ -0,0 +1,431 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cmath> + +#include "ingen/Log.hpp" +#include "ingen/URIMap.hpp" +#include "ingen/URIs.hpp" +#include "ingen/World.hpp" +#include "lv2/atom/util.h" +#include "lv2/midi/midi.h" + +#include "Buffer.hpp" +#include "ControlBindings.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "RunContext.hpp" +#include "ThreadManager.hpp" + +namespace ingen { +namespace server { + +ControlBindings::ControlBindings(Engine& engine) + : _engine(engine) + , _learn_binding(nullptr) + , _bindings(new Bindings()) + , _feedback(new Buffer(*_engine.buffer_factory(), + engine.world()->uris().atom_Sequence, + 0, + 4096)) // FIXME: capacity? +{ + lv2_atom_forge_init( + &_forge, &engine.world()->uri_map().urid_map_feature()->urid_map); +} + +ControlBindings::~ControlBindings() +{ + _feedback.reset(); + delete _learn_binding.load(); +} + +ControlBindings::Key +ControlBindings::port_binding(PortImpl* port) const +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + const ingen::URIs& uris = _engine.world()->uris(); + const Atom& binding = port->get_property(uris.midi_binding); + return binding_key(binding); +} + +ControlBindings::Key +ControlBindings::binding_key(const Atom& binding) const +{ + const ingen::URIs& uris = _engine.world()->uris(); + Key key; + LV2_Atom* num = nullptr; + if (binding.type() == uris.atom_Object) { + const LV2_Atom_Object_Body* obj = (const LV2_Atom_Object_Body*) + binding.get_body(); + if (obj->otype == uris.midi_Bender) { + key = Key(Type::MIDI_BENDER); + } else if (obj->otype == uris.midi_ChannelPressure) { + key = Key(Type::MIDI_CHANNEL_PRESSURE); + } else if (obj->otype == uris.midi_Controller) { + lv2_atom_object_body_get(binding.size(), + obj, + (LV2_URID)uris.midi_controllerNumber, + &num, + nullptr); + if (!num) { + _engine.log().rt_error("Controller binding missing number\n"); + } else if (num->type != uris.atom_Int) { + _engine.log().rt_error("Controller number not an integer\n"); + } else { + key = Key(Type::MIDI_CC, ((LV2_Atom_Int*)num)->body); + } + } else if (obj->otype == uris.midi_NoteOn) { + lv2_atom_object_body_get(binding.size(), + obj, + (LV2_URID)uris.midi_noteNumber, + &num, + nullptr); + if (!num) { + _engine.log().rt_error("Note binding missing number\n"); + } else if (num->type != uris.atom_Int) { + _engine.log().rt_error("Note number not an integer\n"); + } else { + key = Key(Type::MIDI_NOTE, ((LV2_Atom_Int*)num)->body); + } + } + } else if (binding.type()) { + _engine.log().rt_error("Unknown binding type\n"); + } + return key; +} + +ControlBindings::Key +ControlBindings::midi_event_key(uint16_t size, const uint8_t* buf, uint16_t& value) +{ + switch (lv2_midi_message_type(buf)) { + case LV2_MIDI_MSG_CONTROLLER: + value = static_cast<int8_t>(buf[2]); + return Key(Type::MIDI_CC, static_cast<int8_t>(buf[1])); + case LV2_MIDI_MSG_BENDER: + value = (static_cast<int8_t>(buf[2]) << 7) + static_cast<int8_t>(buf[1]); + return Key(Type::MIDI_BENDER); + case LV2_MIDI_MSG_CHANNEL_PRESSURE: + value = static_cast<int8_t>(buf[1]); + return Key(Type::MIDI_CHANNEL_PRESSURE); + case LV2_MIDI_MSG_NOTE_ON: + value = 1.0f; + return Key(Type::MIDI_NOTE, static_cast<int8_t>(buf[1])); + default: + return Key(); + } +} + +bool +ControlBindings::set_port_binding(RunContext& context, + PortImpl* port, + Binding* binding, + const Atom& value) +{ + const Key key = binding_key(value); + if (!!key) { + binding->key = key; + binding->port = port; + _bindings->insert(*binding); + return true; + } else { + return false; + } +} + +void +ControlBindings::port_value_changed(RunContext& ctx, + PortImpl* port, + Key key, + const Atom& value_atom) +{ + ingen::World* world = ctx.engine().world(); + const ingen::URIs& uris = world->uris(); + if (!!key) { + int16_t value = port_value_to_control( + ctx, port, key.type, value_atom); + uint16_t size = 0; + uint8_t buf[4]; + switch (key.type) { + case Type::MIDI_CC: + size = 3; + buf[0] = LV2_MIDI_MSG_CONTROLLER; + buf[1] = key.num; + buf[2] = static_cast<int8_t>(value); + break; + case Type::MIDI_CHANNEL_PRESSURE: + size = 2; + buf[0] = LV2_MIDI_MSG_CHANNEL_PRESSURE; + buf[1] = static_cast<int8_t>(value); + break; + case Type::MIDI_BENDER: + size = 3; + buf[0] = LV2_MIDI_MSG_BENDER; + buf[1] = (value & 0x007F); + buf[2] = (value & 0x7F00) >> 7; + break; + case Type::MIDI_NOTE: + size = 3; + if (value == 1) { + buf[0] = LV2_MIDI_MSG_NOTE_ON; + } else if (value == 0) { + buf[0] = LV2_MIDI_MSG_NOTE_OFF; + } + buf[1] = key.num; + buf[2] = 0x64; // MIDI spec default + break; + default: + break; + } + if (size > 0) { + _feedback->append_event(ctx.nframes() - 1, size, (LV2_URID)uris.midi_MidiEvent, buf); + } + } +} + +void +ControlBindings::start_learn(PortImpl* port) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + Binding* b = _learn_binding.load(); + if (!b) { + _learn_binding = new Binding(Type::NULL_CONTROL, port); + } else { + b->port = port; + } +} + +static void +get_range(RunContext& context, const PortImpl* port, float* min, float* max) +{ + *min = port->minimum().get<float>(); + *max = port->maximum().get<float>(); + if (port->is_sample_rate()) { + *min *= context.engine().sample_rate(); + *max *= context.engine().sample_rate(); + } +} + +float +ControlBindings::control_to_port_value(RunContext& context, + const PortImpl* port, + Type type, + int16_t value) const +{ + float normal = 0.0f; + switch (type) { + case Type::MIDI_CC: + case Type::MIDI_CHANNEL_PRESSURE: + normal = (float)value / 127.0f; + break; + case Type::MIDI_BENDER: + normal = (float)value / 16383.0f; + break; + case Type::MIDI_NOTE: + normal = (value == 0.0f) ? 0.0f : 1.0f; + break; + default: + break; + } + + if (port->is_logarithmic()) { + normal = (expf(normal) - 1.0f) / ((float)M_E - 1.0f); + } + + float min, max; + get_range(context, port, &min, &max); + + return normal * (max - min) + min; +} + +int16_t +ControlBindings::port_value_to_control(RunContext& context, + PortImpl* port, + Type type, + const Atom& value_atom) const +{ + if (value_atom.type() != port->bufs().forge().Float) { + return 0; + } + + float min, max; + get_range(context, port, &min, &max); + + const float value = value_atom.get<float>(); + float normal = (value - min) / (max - min); + + if (normal < 0.0f) { + normal = 0.0f; + } + + if (normal > 1.0f) { + normal = 1.0f; + } + + if (port->is_logarithmic()) { + normal = logf(normal * ((float)M_E - 1.0f) + 1.0); + } + + switch (type) { + case Type::MIDI_CC: + case Type::MIDI_CHANNEL_PRESSURE: + return lrintf(normal * 127.0f); + case Type::MIDI_BENDER: + return lrintf(normal * 16383.0f); + case Type::MIDI_NOTE: + return (value > 0.0f) ? 1 : 0; + default: + return 0; + } +} + +static void +forge_binding(const URIs& uris, + LV2_Atom_Forge* forge, + ControlBindings::Type binding_type, + int32_t value) +{ + LV2_Atom_Forge_Frame frame; + switch (binding_type) { + case ControlBindings::Type::MIDI_CC: + lv2_atom_forge_object(forge, &frame, 0, uris.midi_Controller); + lv2_atom_forge_key(forge, uris.midi_controllerNumber); + lv2_atom_forge_int(forge, value); + break; + case ControlBindings::Type::MIDI_BENDER: + lv2_atom_forge_object(forge, &frame, 0, uris.midi_Bender); + break; + case ControlBindings::Type::MIDI_CHANNEL_PRESSURE: + lv2_atom_forge_object(forge, &frame, 0, uris.midi_ChannelPressure); + break; + case ControlBindings::Type::MIDI_NOTE: + lv2_atom_forge_object(forge, &frame, 0, uris.midi_NoteOn); + lv2_atom_forge_key(forge, uris.midi_noteNumber); + lv2_atom_forge_int(forge, value); + break; + case ControlBindings::Type::MIDI_RPN: // TODO + case ControlBindings::Type::MIDI_NRPN: // TODO + case ControlBindings::Type::NULL_CONTROL: + break; + } +} + +void +ControlBindings::set_port_value(RunContext& context, + PortImpl* port, + Type type, + int16_t value) +{ + float min, max; + get_range(context, port, &min, &max); + + const float val = control_to_port_value(context, port, type, value); + + // TODO: Set port value property so it is saved + port->set_control_value(context, context.start(), val); + + URIs& uris = context.engine().world()->uris(); + context.notify(uris.ingen_value, context.start(), port, + sizeof(float), _forge.Float, &val); +} + +bool +ControlBindings::finish_learn(RunContext& context, Key key) +{ + const ingen::URIs& uris = context.engine().world()->uris(); + Binding* binding = _learn_binding.exchange(nullptr); + if (!binding || (key.type == Type::MIDI_NOTE && !binding->port->is_toggled())) { + return false; + } + + binding->key = key; + _bindings->insert(*binding); + + LV2_Atom buf[16]; + memset(buf, 0, sizeof(buf)); + lv2_atom_forge_set_buffer(&_forge, (uint8_t*)buf, sizeof(buf)); + forge_binding(uris, &_forge, key.type, key.num); + const LV2_Atom* atom = buf; + context.notify(uris.midi_binding, + context.start(), + binding->port, + atom->size, atom->type, LV2_ATOM_BODY_CONST(atom)); + + return true; +} + +void +ControlBindings::get_all(const Raul::Path& path, std::vector<Binding*>& bindings) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + for (Binding& b : *_bindings) { + if (b.port->path() == path || b.port->path().is_child_of(path)) { + bindings.push_back(&b); + } + } +} + +void +ControlBindings::remove(RunContext& ctx, const std::vector<Binding*>& bindings) +{ + for (Binding* b : bindings) { + _bindings->erase(*b); + } +} + +void +ControlBindings::pre_process(RunContext& ctx, Buffer* buffer) +{ + uint16_t value = 0; + ingen::World* world = ctx.engine().world(); + const ingen::URIs& uris = world->uris(); + + _feedback->clear(); + if ((!_learn_binding && _bindings->empty()) || !buffer->get<LV2_Atom>()) { + return; // Don't bother reading input + } + + LV2_Atom_Sequence* seq = buffer->get<LV2_Atom_Sequence>(); + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + if (ev->body.type == uris.midi_MidiEvent) { + const uint8_t* buf = (const uint8_t*)LV2_ATOM_BODY(&ev->body); + const Key key = midi_event_key(ev->body.size, buf, value); + + if (_learn_binding && !!key) { + finish_learn(ctx, key); // Learn new binding + } + + // Set all controls bound to this key + const Binding k = {key, nullptr}; + for (Bindings::const_iterator i = _bindings->lower_bound(k); + i != _bindings->end() && i->key == key; + ++i) { + set_port_value(ctx, i->port, key.type, value); + } + } + } +} + +void +ControlBindings::post_process(RunContext& context, Buffer* buffer) +{ + if (buffer->get<LV2_Atom>()) { + buffer->append_event_buffer(_feedback.get()); + } +} + +} // namespace server +} // namespace ingen |