X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fgeneric_midi%2Fmidicontrollable.cc;h=d78dd5e644091b42c9684d946607eb58676fd6b6;hb=f0fcda204444922fc0e1261929aa6fdb84412036;hp=7bf730b0f5d2fdbaa7833db4398943326bff31de;hpb=959a7909c1adca430a63f783fd16687242a7be3d;p=ardour.git diff --git a/libs/surfaces/generic_midi/midicontrollable.cc b/libs/surfaces/generic_midi/midicontrollable.cc index 7bf730b0f5..d78dd5e644 100644 --- a/libs/surfaces/generic_midi/midicontrollable.cc +++ b/libs/surfaces/generic_midi/midicontrollable.cc @@ -1,6 +1,6 @@ /* Copyright (C) 1998-2006 Paul Davis - + This program 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 @@ -17,34 +17,64 @@ */ -#include /* for sprintf, sigh */ +#include +#include #include -#include -#include -#include -#include +#include + +#include "pbd/error.h" +#include "pbd/controllable_descriptor.h" +#include "pbd/xml++.h" +#include "pbd/stacktrace.h" + +#include "midi++/channel.h" + +#include "ardour/async_midi_port.h" +#include "ardour/automation_control.h" +#include "ardour/midi_ui.h" +#include "ardour/utils.h" #include "midicontrollable.h" +#include "generic_midi_control_protocol.h" -using namespace sigc; +using namespace std; using namespace MIDI; using namespace PBD; using namespace ARDOUR; -MIDIControllable::MIDIControllable (Port& p, Controllable& c, bool is_bistate) - : controllable (c), _port (p), bistate (is_bistate) +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, MIDI::Parser& p, bool m) + : _surface (s) + , controllable (0) + , _descriptor (0) + , _parser (p) + , _momentary (m) { + _learned = false; /* from URI */ setting = false; last_value = 0; // got a better idea ? + last_controllable_value = 0.0f; control_type = none; _control_description = "MIDI Control: none"; control_additional = (byte) -1; - connections = 0; feedback = true; // for now - - /* use channel 0 ("1") as the initial channel */ +} - midi_rebind (0); +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, MIDI::Parser& p, Controllable& c, bool m) + : _surface (s) + , _descriptor (0) + , _parser (p) + , _momentary (m) +{ + set_controllable (&c); + + _learned = true; /* from controllable */ + setting = false; + last_value = 0; // got a better idea ? + last_controllable_value = 0.0f; + control_type = none; + _control_description = "MIDI Control: none"; + control_additional = (byte) -1; + feedback = true; // for now } MIDIControllable::~MIDIControllable () @@ -52,24 +82,57 @@ MIDIControllable::~MIDIControllable () drop_external_control (); } +int +MIDIControllable::init (const std::string& s) +{ + _current_uri = s; + delete _descriptor; + _descriptor = new ControllableDescriptor; + return _descriptor->set (s); +} + void MIDIControllable::midi_forget () { /* stop listening for incoming messages, but retain our existing event + type information. */ - - if (connections > 0) { - midi_sense_connection[0].disconnect (); - } - - if (connections > 1) { - midi_sense_connection[1].disconnect (); - } - - connections = 0; + + midi_sense_connection[0].disconnect (); + midi_sense_connection[1].disconnect (); midi_learn_connection.disconnect (); +} + +void +MIDIControllable::drop_external_control () +{ + midi_forget (); + control_type = none; + control_additional = (byte) -1; +} + +void +MIDIControllable::set_controllable (Controllable* c) +{ + if (c == controllable) { + return; + } + controllable_death_connection.disconnect (); + + controllable = c; + + if (controllable) { + last_controllable_value = controllable->get_value(); + } else { + last_controllable_value = 0.0f; // is there a better value? + } + + if (controllable) { + controllable->Destroyed.connect (controllable_death_connection, MISSING_INVALIDATOR, + boost::bind (&MIDIControllable::drop_controllable, this), + MidiControlUI::instance()); + } } void @@ -86,7 +149,7 @@ void MIDIControllable::learn_about_external_control () { drop_external_control (); - midi_learn_connection = _port.input()->any.connect (mem_fun (*this, &MIDIControllable::midi_receiver)); + _parser.any.connect_same_thread (midi_learn_connection, boost::bind (&MIDIControllable::midi_receiver, this, _1, _2, _3)); } void @@ -95,99 +158,198 @@ MIDIControllable::stop_learning () midi_learn_connection.disconnect (); } -void -MIDIControllable::drop_external_control () +int +MIDIControllable::control_to_midi (float val) { - cerr << "Dropping existing control using " << connections << " connections\n"; + if (controllable->is_gain_like()) { + return gain_to_slider_position (val) * max_value_for_type (); + } + + float control_min = controllable->lower (); + float control_max = controllable->upper (); + const float control_range = control_max - control_min; - if (connections > 0) { - midi_sense_connection[0].disconnect (); - } - if (connections > 1) { - midi_sense_connection[1].disconnect (); + if (controllable->is_toggle()) { + if (val >= (control_min + (control_range/2.0f))) { + return max_value_for_type(); + } else { + return 0; + } } - connections = 0; - midi_learn_connection.disconnect (); + return (val - control_min) / control_range * max_value_for_type (); +} - control_type = none; - control_additional = (byte) -1; +float +MIDIControllable::midi_to_control (int val) +{ + /* fiddle with MIDI value so that we get an odd number of integer steps + and can thus represent "middle" precisely as 0.5. this maps to + the range 0..+1.0 + */ + + float fv = (val == 0 ? 0 : float (val - 1) / (max_value_for_type() - 1)); + + if (controllable->is_gain_like()) { + return slider_position_to_gain (fv); + } + + float control_min = controllable->lower (); + float control_max = controllable->upper (); + const float control_range = control_max - control_min; + + return (fv * control_range) + control_min; } -void -MIDIControllable::midi_sense_note_on (Parser &p, EventTwoBytes *tb) +void +MIDIControllable::midi_sense_note_on (Parser &p, EventTwoBytes *tb) { midi_sense_note (p, tb, true); } -void -MIDIControllable::midi_sense_note_off (Parser &p, EventTwoBytes *tb) +void +MIDIControllable::midi_sense_note_off (Parser &p, EventTwoBytes *tb) { midi_sense_note (p, tb, false); } +int +MIDIControllable::lookup_controllable() +{ + if (!_descriptor) { + return -1; + } + + boost::shared_ptr c = _surface->lookup_controllable (*_descriptor); + + if (!c) { + return -1; + } + + set_controllable (c.get ()); + + return 0; +} + void -MIDIControllable::midi_sense_note (Parser &p, EventTwoBytes *msg, bool is_on) +MIDIControllable::drop_controllable () { - if (!bistate) { - controllable.set_value (msg->note_number/127.0); - } else { + set_controllable (0); +} - /* Note: parser handles the use of zero velocity to - mean note off. if we get called with is_on=true, then we - got a *real* note on. - */ +void +MIDIControllable::midi_sense_note (Parser &, EventTwoBytes *msg, bool /*is_on*/) +{ + if (!controllable) { + if (lookup_controllable()) { + return; + } + } - if (msg->note_number == control_additional) { - controllable.set_value (is_on ? 1 : 0); + if (!controllable->is_toggle()) { + if (control_additional == msg->note_number) { + controllable->set_value (midi_to_control (msg->velocity)); + } + } else { + if (control_additional == msg->note_number) { + controllable->set_value (controllable->get_value() > 0.5f ? 0.0f : 1.0f); } } - last_value = (MIDI::byte) (controllable.get_value() * 127.0); // to prevent feedback fights + last_value = (MIDI::byte) (controllable->get_value() * 127.0); // to prevent feedback fights } void MIDIControllable::midi_sense_controller (Parser &, EventTwoBytes *msg) { + if (!controllable) { + if (lookup_controllable ()) { + return; + } + } + + assert (controllable); + + if (controllable->touching()) { + return; // to prevent feedback fights when e.g. dragging a UI slider + } + if (control_additional == msg->controller_number) { - if (!bistate) { - controllable.set_value (msg->value/127.0); + + if (!controllable->is_toggle()) { + + float new_value = msg->value; + float max_value = max(last_controllable_value, new_value); + float min_value = min(last_controllable_value, new_value); + float range = max_value - min_value; + float threshold = (float) _surface->threshold (); + + bool const in_sync = ( + range < threshold && + controllable->get_value() <= midi_to_control(max_value) && + controllable->get_value() >= midi_to_control(min_value) + ); + + /* If the surface is not motorised, we try to prevent jumps when + the MIDI controller and controllable are out of sync. + There might be a better way of doing this. + */ + + if (in_sync || _surface->motorised ()) { + controllable->set_value (midi_to_control (new_value)); + } + + last_controllable_value = new_value; } else { - if (msg->value > 64.0) { - controllable.set_value (1); + if (msg->value > 64.0f) { + controllable->set_value (1); } else { - controllable.set_value (0); + controllable->set_value (0); } } - last_value = (MIDI::byte) (controllable.get_value() * 127.0); // to prevent feedback fights + last_value = (MIDI::byte) (control_to_midi(controllable->get_value())); // to prevent feedback fights } } void -MIDIControllable::midi_sense_program_change (Parser &p, byte msg) +MIDIControllable::midi_sense_program_change (Parser &, byte msg) { - /* XXX program change messages make no sense for bistates */ + if (!controllable) { + if (lookup_controllable ()) { + return; + } + } - if (!bistate) { - controllable.set_value (msg/127.0); - last_value = (MIDI::byte) (controllable.get_value() * 127.0); // to prevent feedback fights - } + if (!controllable->is_toggle()) { + controllable->set_value (midi_to_control (msg)); + } else if (msg == control_additional) { + controllable->set_value (controllable->get_value() > 0.5f ? 0.0f : 1.0f); + } + + last_value = (MIDI::byte) (controllable->get_value() * 127.0); // to prevent feedback fights } void -MIDIControllable::midi_sense_pitchbend (Parser &p, pitchbend_t pb) +MIDIControllable::midi_sense_pitchbend (Parser &, pitchbend_t pb) { - /* pitchbend messages make no sense for bistates */ + if (!controllable) { + if (lookup_controllable ()) { + return; + } + } - /* XXX gack - get rid of assumption about typeof pitchbend_t */ + if (!controllable->is_toggle()) { + controllable->set_value (midi_to_control (pb)); + } else { + controllable->set_value (controllable->get_value() > 0.5f ? 0.0f : 1.0f); + } - controllable.set_value ((pb/(float) SHRT_MAX)); - last_value = (MIDI::byte) (controllable.get_value() * 127.0); // to prevent feedback fights -} + last_value = control_to_midi (controllable->get_value ()); +} void -MIDIControllable::midi_receiver (Parser &p, byte *msg, size_t len) +MIDIControllable::midi_receiver (Parser &, byte *msg, size_t /*len*/) { /* we only respond to channel messages */ @@ -195,15 +357,11 @@ MIDIControllable::midi_receiver (Parser &p, byte *msg, size_t len) return; } - /* if the our port doesn't do input anymore, forget it ... */ - - if (!_port.input()) { - return; - } - bind_midi ((channel_t) (msg[0] & 0xf), eventType (msg[0] & 0xF0), msg[1]); - controllable.LearningFinished (); + if (controllable) { + controllable->LearningFinished (); + } } void @@ -217,116 +375,85 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) control_channel = chn; control_additional = additional; - if (_port.input() == 0) { - return; - } - - Parser& p = *_port.input(); - int chn_i = chn; switch (ev) { case MIDI::off: - midi_sense_connection[0] = p.channel_note_off[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_note_off)); + _parser.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); - /* if this is a bistate, connect to noteOn as well, + /* if this is a togglee, connect to noteOn as well, and we'll toggle back and forth between the two. */ - if (bistate) { - midi_sense_connection[1] = p.channel_note_on[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_note_on)); - connections = 2; - } else { - connections = 1; - } + if (_momentary) { + _parser.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); + } + _control_description = "MIDI control: NoteOff"; break; case MIDI::on: - midi_sense_connection[0] = p.channel_note_on[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_note_on)); - if (bistate) { - midi_sense_connection[1] = p.channel_note_off[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_note_off)); - connections = 2; - } else { - connections = 1; + _parser.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); + if (_momentary) { + _parser.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); } _control_description = "MIDI control: NoteOn"; break; - + case MIDI::controller: - midi_sense_connection[0] = p.channel_controller[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_controller)); - connections = 1; + _parser.channel_controller[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_controller, this, _1, _2)); snprintf (buf, sizeof (buf), "MIDI control: Controller %d", control_additional); _control_description = buf; break; case MIDI::program: - if (!bistate) { - midi_sense_connection[0] = p.channel_program_change[chn_i].connect - (mem_fun (*this, - &MIDIControllable::midi_sense_program_change)); - connections = 1; - _control_description = "MIDI control: ProgramChange"; - } + _parser.channel_program_change[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_program_change, this, _1, _2)); + _control_description = "MIDI control: ProgramChange"; break; case MIDI::pitchbend: - if (!bistate) { - midi_sense_connection[0] = p.channel_pitchbend[chn_i].connect - (mem_fun (*this, &MIDIControllable::midi_sense_pitchbend)); - connections = 1; - _control_description = "MIDI control: Pitchbend"; - } + _parser.channel_pitchbend[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_pitchbend, this, _1, _2)); + _control_description = "MIDI control: Pitchbend"; break; default: break; } - - cerr << "MIDI bound with " << connections << endl; } -void -MIDIControllable::send_feedback () +MIDI::byte* +MIDIControllable::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool /*force*/) { - byte msg[3]; - - if (setting || !feedback || control_type == none) { - return; + if (!controllable || control_type == none || !feedback || bufsize <= 2) { + return buf; } + + int const gm = control_to_midi (controllable->get_value()); - msg[0] = (control_type & 0xF0) | (control_channel & 0xF); - msg[1] = control_additional; - msg[2] = (byte) (controllable.get_value() * 127.0f); + if (gm == last_value) { + return buf; + } - //_port.write (msg, 3); -} + *buf++ = (0xF0 & control_type) | (0xF & control_channel); + + switch (control_type) { + case MIDI::pitchbend: + *buf++ = int (gm) & 127; + *buf++ = (int (gm) >> 7) & 127; + break; + default: + *buf++ = control_additional; /* controller number */ + *buf++ = gm; + break; + } -MIDI::byte* -MIDIControllable::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool force) -{ - if (control_type != none && feedback && bufsize > 2) { + last_value = gm; + bufsize -= 3; - MIDI::byte gm = (MIDI::byte) (controllable.get_value() * 127.0); - - if (gm != last_value) { - *buf++ = (0xF0 & control_type) | (0xF & control_channel); - *buf++ = control_additional; /* controller number */ - *buf++ = gm; - last_value = gm; - bufsize -= 3; - } - } - return buf; } -int -MIDIControllable::set_state (const XMLNode& node) +int +MIDIControllable::set_state (const XMLNode& node, int /*version*/) { const XMLProperty* prop; int xx; @@ -359,7 +486,7 @@ MIDIControllable::set_state (const XMLNode& node) } bind_midi (control_channel, control_type, control_additional); - + return 0; } @@ -367,16 +494,39 @@ XMLNode& MIDIControllable::get_state () { char buf[32]; - XMLNode& node (controllable.get_state ()); - snprintf (buf, sizeof(buf), "0x%x", (int) control_type); - node.add_property ("event", buf); - snprintf (buf, sizeof(buf), "%d", (int) control_channel); - node.add_property ("channel", buf); - snprintf (buf, sizeof(buf), "0x%x", (int) control_additional); - node.add_property ("additional", buf); - node.add_property ("feedback", (feedback ? "yes" : "no")); + XMLNode* node = new XMLNode ("MIDIControllable"); - return node; + if (_current_uri.empty()) { + node->add_property ("id", controllable->id().to_s()); + } else { + node->add_property ("uri", _current_uri); + } + + if (controllable) { + snprintf (buf, sizeof(buf), "0x%x", (int) control_type); + node->add_property ("event", buf); + snprintf (buf, sizeof(buf), "%d", (int) control_channel); + node->add_property ("channel", buf); + snprintf (buf, sizeof(buf), "0x%x", (int) control_additional); + node->add_property ("additional", buf); + node->add_property ("feedback", (feedback ? "yes" : "no")); + } + + return *node; } +/** @return the maximum value for a control value transmitted + * using a given MIDI::eventType. + */ +int +MIDIControllable::max_value_for_type () const +{ + /* XXX: this is not complete */ + + if (control_type == MIDI::pitchbend) { + return 16383; + } + + return 127; +}