/*
This file is part of Ingen.
Copyright 2007-2012 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
#include "ingen/shared/LV2URIMap.hpp"
#include "ingen/shared/URIs.hpp"
#include "ingen/shared/World.hpp"
#include "lv2/lv2plug.in/ns/ext/atom/util.h"
#include "raul/log.hpp"
#include "raul/midi_events.h"
#include "AudioBuffer.hpp"
#include "ControlBindings.hpp"
#include "Engine.hpp"
#include "Notification.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 Buffer(*_engine.buffer_factory(),
engine.world()->uris()->atom_Sequence,
4096)) // FIXME: capacity?
{
}
ControlBindings::~ControlBindings()
{
_feedback.reset();
}
ControlBindings::Key
ControlBindings::port_binding(PortImpl* port) const
{
ThreadManager::assert_thread(THREAD_PRE_PROCESS);
const Ingen::Shared::URIs& uris = *_engine.world()->uris().get();
const Raul::Atom& binding = port->get_property(uris.ingen_controlBinding);
return binding_key(binding);
}
ControlBindings::Key
ControlBindings::binding_key(const Raul::Atom& binding) const
{
const Ingen::Shared::URIs& uris = *_engine.world()->uris().get();
Key key;
if (binding.type() == _engine.world()->forge().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_NoteOn) {
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, const uint8_t* buf, uint16_t& value)
{
switch (buf[0] & 0xF0) {
case MIDI_CMD_CONTROL:
value = static_cast(buf[2]);
return Key(MIDI_CC, static_cast(buf[1]));
case MIDI_CMD_BENDER:
value = (static_cast(buf[2]) << 7) + static_cast(buf[1]);
return Key(MIDI_BENDER);
case MIDI_CMD_CHANNEL_PRESSURE:
value = static_cast(buf[1]);
return Key(MIDI_CHANNEL_PRESSURE);
case MIDI_CMD_NOTE_ON:
value = 1.0f;
return Key(MIDI_NOTE, static_cast(buf[1]));
default:
return Key();
}
}
void
ControlBindings::port_binding_changed(ProcessContext& context,
PortImpl* port,
const Raul::Atom& binding)
{
const Key key = binding_key(binding);
if (key) {
_bindings->insert(make_pair(key, port));
}
}
void
ControlBindings::port_value_changed(ProcessContext& context,
PortImpl* port,
Key key,
const Raul::Atom& value_atom)
{
Ingen::Shared::World* world = context.engine().world();
const Ingen::Shared::URIs& uris = *world->uris().get();
if (key) {
int16_t value = port_value_to_control(
port, key.type, value_atom, port->minimum(), port->maximum());
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(value);
break;
case MIDI_CHANNEL_PRESSURE:
size = 2;
buf[0] = MIDI_CMD_CHANNEL_PRESSURE;
buf[1] = static_cast(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_event(0, size, uris.midi_MidiEvent.id, buf);
}
}
}
void
ControlBindings::learn(PortImpl* port)
{
ThreadManager::assert_thread(THREAD_PRE_PROCESS);
_learn_port = port;
}
Raul::Atom
ControlBindings::control_to_port_value(Type type,
int16_t value,
const Raul::Atom& min_atom,
const Raul::Atom& max_atom) const
{
float min = min_atom.get_float();
float max = max_atom.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 _engine.world()->forge().make(scaled_value);
}
int16_t
ControlBindings::port_value_to_control(PortImpl* port,
Type type,
const Raul::Atom& value_atom,
const Raul::Atom& min_atom,
const Raul::Atom& max_atom) const
{
if (value_atom.type() != port->bufs().forge().Float)
return 0;
const float min = min_atom.get_float();
const float max = max_atom.get_float();
const float value = value_atom.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(type, value, port->minimum(), port->maximum()));
port->set_value(port_value);
assert(port_value.type() == port->bufs().forge().Float);
assert(dynamic_cast(port->buffer(0).get()));
for (uint32_t v = 0; v < port->poly(); ++v)
reinterpret_cast(port->buffer(v).get())->set_value(
port_value.get_float(), context.start(), context.start());
const Notification note = Notification::make(
Notification::PORT_VALUE, context.start(), port, port_value);
context.event_sink().write(sizeof(note), ¬e);
}
bool
ControlBindings::bind(ProcessContext& context, Key key)
{
const Ingen::Shared::URIs& 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 Notification note = Notification::make(
Notification::PORT_BINDING, context.start(), _learn_port,
context.engine().world()->forge().make(key.num), key.type);
context.event_sink().write(sizeof(note), ¬e);
_learn_port = NULL;
return true;
}
SharedPtr
ControlBindings::remove(const Raul::Path& path)
{
ThreadManager::assert_thread(THREAD_PRE_PROCESS);
SharedPtr old_bindings(_bindings);
SharedPtr 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::remove(PortImpl* port)
{
ThreadManager::assert_thread(THREAD_PRE_PROCESS);
SharedPtr old_bindings(_bindings);
SharedPtr 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, Buffer* buffer)
{
uint16_t value = 0;
SharedPtr bindings = _bindings;
_feedback->clear();
Ingen::Shared::World* world = context.engine().world();
const Ingen::Shared::URIs& uris = *world->uris().get();
if (!_learn_port && bindings->empty()) {
// Don't bother reading input
return;
}
LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)buffer->atom();
LV2_SEQUENCE_FOREACH(seq, i) {
LV2_Atom_Event* const ev = lv2_sequence_iter_get(i);
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_port && key) {
bind(context, key);
}
Bindings::const_iterator i = bindings->find(key);
if (i != bindings->end()) {
set_port_value(context, i->second, key.type, value);
}
}
}
}
void
ControlBindings::post_process(ProcessContext& context, Buffer* buffer)
{
// TODO: merge buffer's existing contents (anything send to it in the patch)
buffer->copy(context, _feedback.get());
}
} // namespace Server
} // namespace Ingen