summaryrefslogtreecommitdiffstats
path: root/src/server/ControlBindings.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/ControlBindings.cpp')
-rw-r--r--src/server/ControlBindings.cpp384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/server/ControlBindings.cpp b/src/server/ControlBindings.cpp
new file mode 100644
index 00000000..6c2cf09c
--- /dev/null
+++ b/src/server/ControlBindings.cpp
@@ -0,0 +1,384 @@
+/* This file is part of Ingen.
+ * Copyright 2009-2011 David Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <math.h>
+#include "raul/log.hpp"
+#include "raul/midi_events.h"
+#include "shared/LV2URIMap.hpp"
+#include "shared/World.hpp"
+#include "events/SendPortValue.hpp"
+#include "events/SendBinding.hpp"
+#include "AudioBuffer.hpp"
+#include "ControlBindings.hpp"
+#include "Engine.hpp"
+#include "EventBuffer.hpp"
+#include "PortImpl.hpp"
+#include "ProcessContext.hpp"
+#include "ThreadManager.hpp"
+
+#define LOG(s) s << "[ControlBindings] "
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Server {
+
+ControlBindings::ControlBindings(Engine& engine)
+ : _engine(engine)
+ , _learn_port(NULL)
+ , _bindings(new Bindings())
+ , _feedback(new EventBuffer(*_engine.buffer_factory(), 1024)) // FIXME: size
+{
+}
+
+ControlBindings::~ControlBindings()
+{
+ delete _feedback;
+}
+
+ControlBindings::Key
+ControlBindings::port_binding(PortImpl* port)
+{
+ const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();
+ const Raul::Atom& binding = port->get_property(uris.ingen_controlBinding);
+ Key key;
+ if (binding.type() == Atom::DICT) {
+ const Atom::DictValue& dict = binding.get_dict();
+ Atom::DictValue::const_iterator t = dict.find(uris.rdf_type);
+ Atom::DictValue::const_iterator n;
+ 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)
+{
+ const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();
+ 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,
+ uris.global_to_event(uris.midi_MidiEvent.id).second,
+ size, buf);
+ }
+ }
+}
+
+void
+ControlBindings::learn(PortImpl* port)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+ _learn_port = port;
+}
+
+Raul::Atom
+ControlBindings::control_to_port_value(PortImpl* port, Type type, int16_t value)
+{
+ const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();
+
+ // 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 = 0.0f;
+ switch (type) {
+ case MIDI_CC:
+ case MIDI_CHANNEL_PRESSURE:
+ normal = (float)value / 127.0f;
+ break;
+ case MIDI_BENDER:
+ normal = (float)value / 16383.0f;
+ break;
+ case MIDI_NOTE:
+ normal = (value == 0.0f) ? 0.0f : 1.0f;
+ break;
+ default:
+ break;
+ }
+
+ float scaled_value = normal * (max - min) + min;
+ if (toggled)
+ scaled_value = (scaled_value < 0.5) ? 0.0 : 1.0;
+
+ 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 Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();
+
+ // 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);
+
+ if (normal < 0.0f) {
+ warn << "Value " << value << " (normal " << normal << ") for "
+ << port->path() << " out of range" << endl;
+ normal = 0.0f;
+ }
+
+ if (normal > 1.0f) {
+ warn << "Value " << value << " (normal " << normal << ") for "
+ << port->path() << " out of range" << endl;
+ 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 Raul::Atom port_value(control_to_port_value(port, type, value));
+ port->set_value(port_value);
+
+ assert(port_value.type() == Atom::FLOAT);
+ assert(dynamic_cast<AudioBuffer*>(port->buffer(0).get()));
+
+ for (uint32_t v = 0; v < port->poly(); ++v)
+ reinterpret_cast<AudioBuffer*>(port->buffer(v).get())->set_value(
+ port_value.get_float(), context.start(), context.start());
+
+ const Events::SendPortValue ev(context.engine(), context.start(), port, true, 0, port_value);
+ context.event_sink().write(sizeof(ev), &ev);
+}
+
+bool
+ControlBindings::bind(ProcessContext& context, Key key)
+{
+ const Ingen::Shared::LV2URIMap& uris = *context.engine().world()->uris().get();
+ assert(_learn_port);
+ 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, _learn_port));
+
+ const Events::SendBinding ev(context.engine(), context.start(), _learn_port, key.type, key.num);
+ context.event_sink().write(sizeof(ev), &ev);
+
+ _learn_port = NULL;
+ return true;
+}
+
+SharedPtr<ControlBindings::Bindings>
+ControlBindings::remove(const Raul::Path& path)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ SharedPtr<Bindings> old_bindings(_bindings);
+ SharedPtr<Bindings> copy(new Bindings(*_bindings.get()));
+
+ for (Bindings::iterator i = copy->begin(); i != copy->end();) {
+ Bindings::iterator next = i;
+ ++next;
+
+ if (i->second->path() == path || i->second->path().is_child_of(path))
+ copy->erase(i);
+
+ i = next;
+ }
+
+ _bindings = copy;
+ return old_bindings;
+}
+
+SharedPtr<ControlBindings::Bindings>
+ControlBindings::remove(PortImpl* port)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ SharedPtr<Bindings> old_bindings(_bindings);
+ SharedPtr<Bindings> copy(new Bindings(*_bindings.get()));
+
+ for (Bindings::iterator i = copy->begin(); i != copy->end();) {
+ Bindings::iterator next = i;
+ ++next;
+
+ if (i->second == port)
+ copy->erase(i);
+
+ i = next;
+ }
+
+ _bindings = copy;
+ return old_bindings;
+}
+
+void
+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();
+
+ const Ingen::Shared::LV2URIMap& uris = *context.engine().world()->uris().get();
+
+ // TODO: cache
+ const uint32_t midi_event_type = uris.global_to_event(uris.midi_MidiEvent.id).second;
+
+ // Learn from input if necessary
+ if (_learn_port) {
+ for (buffer->rewind();
+ buffer->get_event(&frames, &subframes, &type, &size, &buf);
+ buffer->increment()) {
+ if (type != midi_event_type)
+ continue;
+
+ const Key key = midi_event_key(size, buf, value);
+ if (key && bind(context, key))
+ break;
+ }
+ }
+
+ // 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 != midi_event_type)
+ 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 Server
+} // namespace Ingen