/*
This file is part of Ingen.
Copyright 2007-2017 David Robillard
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 .
*/
#include "ControlBindings.hpp"
#include "Buffer.hpp"
#include "BufferFactory.hpp"
#include "Engine.hpp"
#include "PortImpl.hpp"
#include "RunContext.hpp"
#include "ThreadManager.hpp"
#include "ingen/Atom.hpp"
#include "ingen/Forge.hpp"
#include "ingen/Log.hpp"
#include "ingen/URIMap.hpp"
#include "ingen/URIs.hpp"
#include "ingen/World.hpp"
#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "raul/Path.hpp"
#include
#include
#include
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(buf[2]);
return Key(Type::MIDI_CC, static_cast(buf[1]));
case LV2_MIDI_MSG_BENDER:
value = (static_cast(buf[2]) << 7) + static_cast(buf[1]);
return Key(Type::MIDI_BENDER);
case LV2_MIDI_MSG_CHANNEL_PRESSURE:
value = static_cast(buf[1]);
return Key(Type::MIDI_CHANNEL_PRESSURE);
case LV2_MIDI_MSG_NOTE_ON:
value = 1.0f;
return Key(Type::MIDI_NOTE, static_cast(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)
{
const ingen::URIs& uris = ctx.engine().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(value);
break;
case Type::MIDI_CHANNEL_PRESSURE:
size = 2;
buf[0] = LV2_MIDI_MSG_CHANNEL_PRESSURE;
buf[1] = static_cast(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();
*max = port->maximum().get();
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 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& 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& bindings)
{
for (Binding* b : bindings) {
_bindings->erase(*b);
}
}
void
ControlBindings::pre_process(RunContext& ctx, Buffer* buffer)
{
uint16_t value = 0;
const ingen::URIs& uris = ctx.engine().world().uris();
_feedback->clear();
if ((!_learn_binding && _bindings->empty()) || !buffer->get()) {
return; // Don't bother reading input
}
LV2_Atom_Sequence* seq = buffer->get();
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()) {
buffer->append_event_buffer(_feedback.get());
}
}
} // namespace server
} // namespace ingen