X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fgeneric_midi%2Fmidicontrollable.cc;h=d36ccefd44eb44b2a206a7723771c24fb3324152;hb=6e485c0fc1425de78e21ea1aeec6233b136681dd;hp=d960140958d7d6939375f73e6acc1cb46d174aec;hpb=6c717a56e28ebc67bcf041a1ee9887d46700a0e7;p=ardour.git diff --git a/libs/surfaces/generic_midi/midicontrollable.cc b/libs/surfaces/generic_midi/midicontrollable.cc index d960140958..d36ccefd44 100644 --- a/libs/surfaces/generic_midi/midicontrollable.cc +++ b/libs/surfaces/generic_midi/midicontrollable.cc @@ -17,54 +17,60 @@ */ -#define __STDC_FORMAT_MACROS 1 #include - -#include /* for sprintf, sigh */ +#include #include +#include #include "pbd/error.h" #include "pbd/controllable_descriptor.h" #include "pbd/xml++.h" +#include "pbd/stacktrace.h" #include "midi++/port.h" #include "midi++/channel.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 std; using namespace MIDI; using namespace PBD; using namespace ARDOUR; -MIDIControllable::MIDIControllable (Port& p, bool is_bistate) - : controllable (0) +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, bool m) + : _surface (s) + , controllable (0) , _descriptor (0) , _port (p) - , bistate (is_bistate) + , _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; feedback = true; // for now } -MIDIControllable::MIDIControllable (Port& p, Controllable& c, bool is_bistate) - : controllable (&c) +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, Controllable& c, bool m) + : _surface (s) , _descriptor (0) , _port (p) - , bistate (is_bistate) - + , _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; @@ -108,7 +114,25 @@ MIDIControllable::drop_external_control () 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 @@ -125,7 +149,7 @@ void MIDIControllable::learn_about_external_control () { drop_external_control (); - _port.input()->any.connect_same_thread (midi_learn_connection, boost::bind (&MIDIControllable::midi_receiver, this, _1, _2, _3)); + _port.parser()->any.connect_same_thread (midi_learn_connection, boost::bind (&MIDIControllable::midi_receiver, this, _1, _2, _3)); } void @@ -134,44 +158,47 @@ MIDIControllable::stop_learning () midi_learn_connection.disconnect (); } -float -MIDIControllable::control_to_midi(float val) +int +MIDIControllable::control_to_midi (float val) { - float control_min = 0.0f; - float control_max = 1.0f; - ARDOUR::AutomationControl* ac = dynamic_cast(controllable); - if (ac) { - control_min = ac->parameter().min(); - control_max = ac->parameter().max(); - } + 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; - const float midi_range = 127.0f; // TODO: NRPN etc. - return (val - control_min) / control_range * midi_range; + if (controllable->is_toggle()) { + if (val >= (control_min + (control_range/2.0f))) { + return max_value_for_type(); + } else { + return 0; + } + } + + return (val - control_min) / control_range * max_value_for_type (); } float -MIDIControllable::midi_to_control(float val) +MIDIControllable::midi_to_control (int val) { - float control_min = 0.0f; - float control_max = 1.0f; - ARDOUR::AutomationControl* ac = dynamic_cast(controllable); + /* 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 + */ - const float midi_range = 127.0f; // TODO: NRPN etc. - - if (ac) { + float fv = (val == 0 ? 0 : float (val - 1) / (max_value_for_type() - 1)); - if (ac->is_gain_like()) { - return slider_position_to_gain (val/midi_range); - } - - control_min = ac->parameter().min(); - control_max = ac->parameter().max(); - } + 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 val / midi_range * control_range + control_min; + + return (fv * control_range) + control_min; } void @@ -186,24 +213,46 @@ 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::drop_controllable () +{ + set_controllable (0); +} + void -MIDIControllable::midi_sense_note (Parser &, EventTwoBytes *msg, bool is_on) +MIDIControllable::midi_sense_note (Parser &, EventTwoBytes *msg, bool /*is_on*/) { if (!controllable) { - return; + if (lookup_controllable()) { + return; + } } - if (!bistate) { - controllable->set_value (msg->note_number/127.0); + if (!controllable->is_toggle()) { + if (control_additional == msg->note_number) { + controllable->set_value (midi_to_control (msg->velocity)); + } } else { - - /* 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. - */ - - if (msg->note_number == control_additional) { - controllable->set_value (is_on ? 1 : 0); + if (control_additional == msg->note_number) { + controllable->set_value (controllable->get_value() > 0.5f ? 0.0f : 1.0f); } } @@ -214,18 +263,45 @@ void MIDIControllable::midi_sense_controller (Parser &, EventTwoBytes *msg) { if (!controllable) { - return; + 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 (midi_to_control(msg->value)); + + 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) { + if (msg->value > 64.0f) { controllable->set_value (1); } else { controllable->set_value (0); @@ -240,29 +316,36 @@ void MIDIControllable::midi_sense_program_change (Parser &, byte msg) { if (!controllable) { - return; + if (lookup_controllable ()) { + return; + } } - /* XXX program change messages make no sense for bistates */ - 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 &, pitchbend_t pb) { if (!controllable) { - return; + if (lookup_controllable ()) { + return; + } } - /* pitchbend messages make no sense for bistates */ - - /* 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 @@ -276,13 +359,15 @@ MIDIControllable::midi_receiver (Parser &, byte *msg, size_t /*len*/) /* if the our port doesn't do input anymore, forget it ... */ - if (!_port.input()) { + if (!_port.parser()) { return; } bind_midi ((channel_t) (msg[0] & 0xf), eventType (msg[0] & 0xF0), msg[1]); - controllable->LearningFinished (); + if (controllable) { + controllable->LearningFinished (); + } } void @@ -296,22 +381,22 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) control_channel = chn; control_additional = additional; - if (_port.input() == 0) { + if (_port.parser() == 0) { return; } - Parser& p = *_port.input(); + Parser& p = *_port.parser(); int chn_i = chn; switch (ev) { case MIDI::off: p.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) { + if (_momentary) { p.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); } @@ -320,7 +405,7 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) case MIDI::on: p.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); - if (bistate) { + if (_momentary) { p.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"; @@ -333,17 +418,13 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) break; case MIDI::program: - if (!bistate) { - p.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"; - } + p.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) { - p.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"; - } + p.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: @@ -351,38 +432,35 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) } } -void -MIDIControllable::send_feedback () -{ - byte msg[3]; - - if (setting || !feedback || control_type == none) { - return; - } - - msg[0] = (control_type & 0xF0) | (control_channel & 0xF); - msg[1] = control_additional; - msg[2] = (byte) (control_to_midi(controllable->get_value())); - - _port.write (msg, 3, 0); -} - MIDI::byte* MIDIControllable::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool /*force*/) { - if (control_type != none && feedback && bufsize > 2) { + if (!controllable || control_type == none || !feedback || bufsize <= 2) { + return buf; + } + + int const gm = control_to_midi (controllable->get_value()); - MIDI::byte gm = (MIDI::byte) (control_to_midi(controllable->get_value())); + if (gm == last_value) { + return buf; + } - if (gm != last_value) { - *buf++ = (0xF0 & control_type) | (0xF & control_channel); - *buf++ = control_additional; /* controller number */ - *buf++ = gm; - last_value = gm; - bufsize -= 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; } + last_value = gm; + bufsize -= 3; + return buf; } @@ -431,9 +509,11 @@ MIDIControllable::get_state () XMLNode* node = new XMLNode ("MIDIControllable"); - if (!_current_uri.empty()) { + 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); @@ -448,3 +528,17 @@ MIDIControllable::get_state () 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; +}