Add motorised attribute to DeviceInfo for generic MIDI maps so that

we can specify if a surface is motorised, and as such will keep its
phyiscal controls in sync with Ardour's controllables at all times.
If this is not the case, we enable the code to avoid jumps when controls and
controllables are out of sync.  Mark the BCF2000 as motorised.



git-svn-id: svn://localhost/ardour2/branches/3.0@11611 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington 2012-03-07 01:11:22 +00:00
parent 208703da53
commit fb6895ba86
6 changed files with 57 additions and 27 deletions

View File

@ -55,9 +55,9 @@ using namespace std;
GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
: ControlProtocol (s, _("Generic MIDI"), midi_ui_context())
, _motorised (false)
, gui (0)
{
_input_port = MIDI::Manager::instance()->midi_input_port ();
_output_port = MIDI::Manager::instance()->midi_output_port ();
@ -320,7 +320,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c)
}
if (!mc) {
mc = new MIDIControllable (*_input_port, *c, false);
mc = new MIDIControllable (this, *_input_port, *c, false);
}
{
@ -417,7 +417,7 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos,
MIDI::byte value = control_number;
// Create a MIDIControllable
MIDIControllable* mc = new MIDIControllable (*_input_port, *control, false);
MIDIControllable* mc = new MIDIControllable (this, *_input_port, *control, false);
// Remove any old binding for this midi channel/type/value pair
// Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
@ -533,7 +533,7 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
cerr << "\tresult = " << c << endl;
if (c) {
MIDIControllable* mc = new MIDIControllable (*_input_port, *c, false);
MIDIControllable* mc = new MIDIControllable (this, *_input_port, *c, false);
if (mc->set_state (**niter, version) == 0) {
controllables.push_back (mc);
@ -622,6 +622,12 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath)
_bank_size = atoi (prop->value());
_current_bank = 0;
}
if ((prop = (*citer)->property ("motorised")) != 0) {
_motorised = string_is_affirmative (prop->value ());
} else {
_motorised = false;
}
}
if ((*citer)->name() == "Binding") {
@ -714,7 +720,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
prop = node.property (X_("uri"));
uri = prop->value();
MIDIControllable* mc = new MIDIControllable (*_input_port, momentary);
MIDIControllable* mc = new MIDIControllable (this, *_input_port, momentary);
if (mc->init (uri)) {
delete mc;

View File

@ -81,6 +81,10 @@ class GenericMidiControlProtocol : public ARDOUR::ControlProtocol {
void next_bank ();
void prev_bank ();
bool motorised () const {
return _motorised;
}
private:
MIDI::Port* _input_port;
MIDI::Port* _output_port;
@ -124,6 +128,12 @@ class GenericMidiControlProtocol : public ARDOUR::ControlProtocol {
std::string _current_binding;
uint32_t _bank_size;
uint32_t _current_bank;
/** true if this surface is motorised. If it is, we assume
that the surface's controls are never out of sync with
Ardour's state, so we don't have to take steps to avoid
values jumping around when things are not in sync.
*/
bool _motorised;
mutable void *gui;
void build_gui ();

View File

@ -33,14 +33,16 @@
#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 m)
: controllable (0)
MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, bool m)
: _surface (s)
, controllable (0)
, _descriptor (0)
, _port (p)
, _momentary (m)
@ -55,8 +57,9 @@ MIDIControllable::MIDIControllable (Port& p, bool m)
feedback = true; // for now
}
MIDIControllable::MIDIControllable (Port& p, Controllable& c, bool m)
: controllable (&c)
MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, Controllable& c, bool m)
: _surface (s)
, controllable (&c)
, _descriptor (0)
, _port (p)
, _momentary (m)
@ -134,7 +137,7 @@ MIDIControllable::stop_learning ()
midi_learn_connection.disconnect ();
}
float
int
MIDIControllable::control_to_midi (float val)
{
if (controllable->is_gain_like()) {
@ -149,24 +152,24 @@ MIDIControllable::control_to_midi (float val)
}
float
MIDIControllable::midi_to_control (float val)
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
*/
val = (val == 0.0f ? 0.0f : (val-1.0f) / (max_value_for_type() - 1));
float fv = (val == 0 ? 0 : float (val - 1) / (max_value_for_type() - 1));
if (controllable->is_gain_like()) {
return slider_position_to_gain (val);
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 * control_range) + control_min;
return (fv * control_range) + control_min;
}
void
@ -219,11 +222,19 @@ MIDIControllable::midi_sense_controller (Parser &, EventTwoBytes *msg)
float range = max_value - min_value;
float threshold = 10;
// prevent jumps when MIDI controller and controllable are "out of sync"
if (range < threshold &&
controllable->get_value() <= midi_to_control(max_value) &&
controllable->get_value() >= midi_to_control(min_value)) {
controllable->set_value (midi_to_control (new_value) );
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;
@ -360,7 +371,7 @@ MIDIControllable::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool /*forc
return buf;
}
float const gm = control_to_midi (controllable->get_value());
int const gm = control_to_midi (controllable->get_value());
if (gm == last_value) {
return buf;

View File

@ -40,11 +40,13 @@ namespace MIDI {
class Parser;
}
class GenericMidiControlProtocol;
class MIDIControllable : public PBD::Stateful
{
public:
MIDIControllable (MIDI::Port&, PBD::Controllable&, bool momentary);
MIDIControllable (MIDI::Port&, bool momentary = false);
MIDIControllable (GenericMidiControlProtocol *, MIDI::Port&, PBD::Controllable&, bool momentary);
MIDIControllable (GenericMidiControlProtocol *, MIDI::Port&, bool momentary = false);
virtual ~MIDIControllable ();
int init (const std::string&);
@ -65,8 +67,8 @@ class MIDIControllable : public PBD::Stateful
bool get_midi_feedback () { return feedback; }
void set_midi_feedback (bool val) { feedback = val; }
float control_to_midi(float val);
float midi_to_control(float val);
int control_to_midi(float val);
float midi_to_control(int val);
bool learned() const { return _learned; }
@ -90,7 +92,8 @@ class MIDIControllable : public PBD::Stateful
private:
int max_value_for_type () const;
GenericMidiControlProtocol* _surface;
PBD::Controllable* controllable;
PBD::ControllableDescriptor* _descriptor;
std::string _current_uri;

View File

@ -5,7 +5,7 @@
<!-- Set the BCF2000 to factory preset number 2, and this will bind -->
<!-- the controllers intuitively to the DAW controllers. -->
<!-- -->
<DeviceInfo bank-size="8"/>
<DeviceInfo bank-size="8" motorised="yes"/>
<!-- Channel controls: -->
<!-- - the rotary encoder, when pushed, will -->

View File

@ -4,7 +4,7 @@
<!-- Adapted by Carl Hetherington -->
<!-- Map for the Behringer BCF2000 in Mackie Control emulation mode -->
<DeviceInfo bank-size="8"/>
<DeviceInfo bank-size="8" motorised="yes"/>
<!-- Channel controls: -->
<!-- - the rotary encoder, when pushed, will -->