move all code to construct MIDI messages into relevant Control/Strip/Surface object; remove MackieMidiBuilder

git-svn-id: svn://localhost/ardour2/branches/3.0@11895 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2012-04-11 13:03:41 +00:00
parent 0860570c8c
commit 02c8ccf348
23 changed files with 374 additions and 569 deletions

View file

@ -103,7 +103,7 @@ public:
virtual Led & led() { return _led; }
virtual type_t type() const { return type_button; };
MidiByteArray zero() { return _led.set_state (off); }
MidiByteArray update_message () const;

View file

@ -22,13 +22,11 @@
#include "controls.h"
#include "types.h"
#include "mackie_midi_builder.h"
#include "surface.h"
#include "control_group.h"
#include "button.h"
#include "led.h"
#include "ledring.h"
#include "pot.h"
#include "fader.h"
#include "jog.h"
@ -75,10 +73,6 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control)
os << " { ";
os << "name: " << control.name();
os << ", ";
os << "id: " << "0x" << setw(4) << setfill('0') << hex << control.id() << setfill(' ');
os << ", ";
os << "type: " << "0x" << setw(2) << setfill('0') << hex << control.type() << setfill(' ');
os << ", ";
os << "raw_id: " << "0x" << setw(2) << setfill('0') << hex << control.raw_id() << setfill(' ');
os << ", ";
os << "group: " << control.group().name();
@ -87,16 +81,6 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control)
return os;
}
Control*
Pot::factory (Surface& surface, int id, const char* name, Group& group)
{
Pot* p = new Pot (id, name, group);
surface.pots[id] = p;
surface.controls.push_back (p);
group.add (*p);
return p;
}
Control*
Jog::factory (Surface& surface, int id, const char* name, Group& group)
{

View file

@ -28,30 +28,21 @@
#include "pbd/signals.h"
#include "mackie_control_exception.h"
#include "midi_byte_array.h"
namespace Mackie
{
class Strip;
class Group;
class Led;
class Surface;
class Control
{
public:
enum type_t {
type_led,
type_led_ring,
type_fader = 0xe0,
type_button = 0x90,
type_pot = 0xb0,
type_meter = 0xd0
};
enum base_id_t {
fader_base_id = 0x0,
pot_base_id = 0x10,
fader_base_id = 0xe0,
pot_base_id = 0x30,
jog_base_id = 0x3c,
fader_touch_button_base_id = 0xe0,
vselect_button_base_id = 0x20,
@ -65,12 +56,6 @@ public:
Control (int id, std::string name, Group& group);
virtual ~Control() {}
virtual Led & led() { throw MackieControlException ("no led available"); }
/// type() << 8 + midi id of the control. This
/// provides a unique id for any control on the surface.
int id() const { return (type() << 8) + _id; }
/// the value of the second bytes of the message. It's
/// the id of the control, but only guaranteed to be
/// unique within the control type.
@ -78,19 +63,17 @@ public:
const std::string & name() const { return _name; }
Group & group() const { return _group; }
virtual bool accepts_feedback() const { return true; }
virtual type_t type() const = 0;
/// Return true if this control is the one and only Jog Wheel
virtual bool is_jog() const { return false; }
bool in_use () const;
void set_in_use (bool);
/// Keep track of the timeout so it can be updated with more incoming events
sigc::connection in_use_connection;
virtual MidiByteArray zero() = 0;
/** If we are doing an in_use timeout for a fader without touch, this
* is its touch button control; otherwise 0.
*/

View file

@ -34,3 +34,16 @@ Fader::factory (Surface& surface, int id, const char* name, Group& group)
return f;
}
MidiByteArray
Fader::set_position (float normalized)
{
position = normalized;
return update_message ();
}
MidiByteArray
Fader::update_message ()
{
int posi = int (0x3fff * position);
return MidiByteArray (3, raw_id(), posi & 0x7f, posi >> 7);
}

View file

@ -10,12 +10,19 @@ class Fader : public Control
public:
Fader (int id, std::string name, Group & group)
: Control (id, name, group)
, position (0.0)
{
}
virtual type_t type() const { return type_fader; }
MidiByteArray set_position (float);
MidiByteArray zero() { return set_position (0.0); }
MidiByteArray update_message ();
static Control* factory (Surface&, int id, const char*, Group&);
private:
float position;
};
}

View file

@ -33,7 +33,7 @@ public:
{
}
virtual bool is_jog() const { return true; }
MidiByteArray zero() { return MidiByteArray(); }
static Control* factory (Surface&, int id, const char*, Group&);
};

View file

@ -36,9 +36,10 @@ public:
}
Led & led() { return *this; }
type_t type() const { return type_led; }
MidiByteArray set_state (LedState);
MidiByteArray zero() { return set_state (off); }
static Control* factory (Surface&, int id, const char*, Group&);
private:

View file

@ -1,41 +0,0 @@
/*
Copyright (C) 2006,2007 John Anderson
Copyright (C) 2012 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
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_mackie_control_protocol_ledring_h__
#define __ardour_mackie_control_protocol_ledring_h__
#include "controls.h"
#include "led.h"
namespace Mackie {
class LedRing : public Led
{
public:
LedRing (int id, std::string name, Group & group)
: Led (id, name, group)
{
}
virtual type_t type() const { return type_led_ring; }
};
}
#endif

View file

@ -55,7 +55,6 @@
#include "midi_byte_array.h"
#include "mackie_control_exception.h"
#include "mackie_midi_builder.h"
#include "surface_port.h"
#include "surface.h"
@ -365,8 +364,6 @@ MackieControlProtocol::periodic ()
(*s)->periodic ();
}
update_timecode_display();
return true;
}

View file

@ -33,12 +33,12 @@
#include "midi++/types.h"
#include "ardour/types.h"
#include "control_protocol/control_protocol.h"
#include "types.h"
#include "midi_byte_array.h"
#include "controls.h"
#include "mackie_jog_wheel.h"
#include "mackie_midi_builder.h"
#include "timer.h"
namespace MIDI {
@ -371,8 +371,6 @@ class MackieControlProtocol
int _modifier_state;
Mackie::MackieMidiBuilder builder;
typedef std::list<GSource*> PortSources;
PortSources port_sources;

View file

@ -3,7 +3,6 @@
#include "ardour/session.h"
#include "mackie_jog_wheel.h"
#include "mackie_control_protocol.h"
#include "surface_port.h"
#include "controls.h"

View file

@ -1,276 +0,0 @@
/*
Copyright (C) 2006,2007 John Anderson
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
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "mackie_midi_builder.h"
#include <typeinfo>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include "pbd/compose.h"
#include "ardour/debug.h"
#include "controls.h"
#include "control_group.h"
#include "strip.h"
#include "button.h"
#include "led.h"
#include "ledring.h"
#include "pot.h"
#include "fader.h"
#include "jog.h"
#include "meter.h"
#include "midi_byte_array.h"
#include "surface.h"
#include "surface_port.h"
using namespace PBD;
using namespace Mackie;
using namespace std;
#define NUCLEUS_DEBUG 1
MIDI::byte MackieMidiBuilder::calculate_pot_value (midi_pot_mode mode, const ControlState & state)
{
// TODO do an exact calc for 0.50? To allow manually re-centering the port.
// center on or off
MIDI::byte retval = (state.pos > 0.45 && state.pos < 0.55 ? 1 : 0) << 6;
// mode
retval |= (mode << 4);
// value, but only if off hasn't explicitly been set
if (state.led_state != off)
retval += (int(state.pos * 10.0) + 1) & 0x0f; // 0b00001111
return retval;
}
MidiByteArray MackieMidiBuilder::build_led_ring (const Pot & pot, const ControlState & state, midi_pot_mode mode )
{
return build_led_ring (pot.led_ring(), state, mode);
}
MidiByteArray MackieMidiBuilder::build_led_ring (const LedRing & led_ring, const ControlState & state, midi_pot_mode mode)
{
// The other way of doing this:
// 0x30 + pot/ring number (0-7)
//, 0x30 + led_ring.ordinal() - 1
return MidiByteArray (3
// the control type
, midi_pot_id
// the id
, 0x20 + led_ring.raw_id()
// the value
, calculate_pot_value (mode, state)
);
}
MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos)
{
int posi = int (0x3fff * pos);
return MidiByteArray (3
, midi_fader_id | fader.raw_id()
// lower-order bits
, posi & 0x7f
// higher-order bits
, (posi >> 7)
);
}
MidiByteArray MackieMidiBuilder::zero_strip (Surface& surface, const Strip & strip)
{
Group::Controls::const_iterator it = strip.controls().begin();
MidiByteArray retval;
for (; it != strip.controls().end(); ++it) {
Control & control = **it;
if (control.accepts_feedback())
retval << zero_control (control);
}
// These must have sysex headers
/* XXX: not sure about this check to only display stuff for strips of index < 8 */
if (strip.index() < 8) {
retval << strip_display_blank (surface, strip, 0);
retval << strip_display_blank (surface, strip, 1);
}
return retval;
}
MidiByteArray MackieMidiBuilder::zero_control (Control & control)
{
switch (control.type()) {
case Control::type_button:
return control.led().set_state (off);
case Control::type_led:
return dynamic_cast<Led&>(control).set_state (off);
case Control::type_fader:
return build_fader ((Fader&)control, 0.0);
case Control::type_pot:
return build_led_ring (dynamic_cast<const Pot&> (control), off);
case Control::type_led_ring:
return build_led_ring (dynamic_cast<const LedRing&> (control), off);
case Control::type_meter:
return const_cast<Meter&>(dynamic_cast<const Meter&>(control)).update_message (0.0);
default:
ostringstream os;
os << "Unknown control type " << control << " in Strip::zero_control";
throw MackieControlException (os.str());
}
}
char translate_seven_segment (char achar)
{
achar = toupper (achar);
if (achar >= 0x40 && achar <= 0x60)
return achar - 0x40;
else if (achar >= 0x21 && achar <= 0x3f)
return achar;
else
return 0x00;
}
MidiByteArray MackieMidiBuilder::two_char_display (const std::string & msg, const std::string & dots)
{
if (msg.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: msg must be exactly 2 characters");
if (dots.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: dots must be exactly 2 characters");
MidiByteArray bytes (6, 0xb0, 0x4a, 0x00, 0xb0, 0x4b, 0x00);
// chars are understood by the surface in right-to-left order
// could also exchange the 0x4a and 0x4b, above
bytes[5] = translate_seven_segment (msg[0]) + (dots[0] == '.' ? 0x40 : 0x00);
bytes[2] = translate_seven_segment (msg[1]) + (dots[1] == '.' ? 0x40 : 0x00);
return bytes;
}
MidiByteArray MackieMidiBuilder::two_char_display (unsigned int value, const std::string & /*dots*/)
{
ostringstream os;
os << setfill('0') << setw(2) << value % 100;
return two_char_display (os.str());
}
MidiByteArray MackieMidiBuilder::strip_display_blank (Surface& surface, const Strip & strip, unsigned int line_number)
{
// 6 spaces, not 7 because strip_display adds a space where appropriate
return strip_display (surface, strip, line_number, " ");
}
MidiByteArray MackieMidiBuilder::strip_display (Surface& surface, const Strip & strip, unsigned int line_number, const std::string & line)
{
assert (line_number <= 1);
MidiByteArray retval;
uint32_t index = strip.index() % 8;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display index: %1, line %2 = %3\n", strip.index(), line_number, line));
// sysex header
retval << surface.sysex_hdr();
// code for display
retval << 0x12;
// offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
retval << (index * 7 + (line_number * 0x38));
// ascii data to display
retval << line;
// pad with " " out to 6 chars
for (int i = line.length(); i < 6; ++i) {
retval << ' ';
}
// column spacer, unless it's the right-hand column
if (strip.index() < 7) {
retval << ' ';
}
// sysex trailer
retval << MIDI::eox;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display midi: %1\n", retval));
return retval;
}
MidiByteArray MackieMidiBuilder::all_strips_display (SurfacePort & /*port*/, std::vector<std::string> & /*lines1*/, std::vector<std::string> & /*lines2*/)
{
MidiByteArray retval;
retval << 0x12 << 0;
// NOTE remember max 112 bytes per message, including sysex headers
retval << "Not working yet";
return retval;
}
MidiByteArray
MackieMidiBuilder::timecode_display (Surface& surface, const std::string & timecode, const std::string & last_timecode)
{
// if there's no change, send nothing, not even sysex header
if (timecode == last_timecode) return MidiByteArray();
// length sanity checking
string local_timecode = timecode;
// truncate to 10 characters
if (local_timecode.length() > 10) {
local_timecode = local_timecode.substr (0, 10);
}
// pad to 10 characters
while (local_timecode.length() < 10) {
local_timecode += " ";
}
// find the suffix of local_timecode that differs from last_timecode
std::pair<string::const_iterator,string::iterator> pp = mismatch (last_timecode.begin(), last_timecode.end(), local_timecode.begin());
MidiByteArray retval;
// sysex header
retval << surface.sysex_hdr();
// code for timecode display
retval << 0x10;
// translate characters. These are sent in reverse order of display
// hence the reverse iterators
string::reverse_iterator rend = reverse_iterator<string::iterator> (pp.second);
for (string::reverse_iterator it = local_timecode.rbegin(); it != rend; ++it) {
retval << translate_seven_segment (*it);
}
// sysex trailer
retval << MIDI::eox;
return retval;
}

View file

@ -1,111 +0,0 @@
/*
Copyright (C) 2006,2007 John Anderson
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
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef mackie_midi_builder_h
#define mackie_midi_builder_h
#include "midi_byte_array.h"
#include "types.h"
#include "controls.h"
namespace Mackie
{
class SurfacePort;
class Button;
class Meter;
class Fader;
class Jog;
class Pot;
class Led;
class LedRing;
/**
This knows how to build midi messages given a control and
a state.
*/
class MackieMidiBuilder
{
public:
MackieMidiBuilder () {}
~MackieMidiBuilder() {}
/**
The first byte of a midi message from the surface
will contain one of these, sometimes bitmasked
with the control id
*/
enum midi_types {
midi_fader_id = Control::type_fader,
midi_button_id = Control::type_button,
midi_pot_id = Control::type_pot,
};
/**
The LED rings have these modes.
*/
enum midi_pot_mode {
midi_pot_mode_dot = 0,
midi_pot_mode_boost_cut = 1,
midi_pot_mode_wrap = 2,
midi_pot_mode_spread = 3,
};
MidiByteArray build_led_ring (const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot);
MidiByteArray build_led_ring (const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot);
MidiByteArray build_fader (const Fader & fader, float pos);
/// return bytes that will reset all controls to their zero positions
/// And blank the display for the strip. Pass Surface so we know which sysex header to use.
MidiByteArray zero_strip (Surface&, const Strip & strip);
// provide bytes to zero the given control
MidiByteArray zero_control (Control & control);
// display the first 2 chars of the msg in the 2 char display
// . is appended to the previous character, so A.B. would
// be two characters
MidiByteArray two_char_display (const std::string & msg, const std::string & dots = " ");
MidiByteArray two_char_display (unsigned int value, const std::string & dots = " ");
/**
Timecode display. Only the difference between timecode and last_timecode will
be encoded, to save midi bandwidth. If they're the same, an empty array will
be returned
*/
MidiByteArray timecode_display (Surface&, const std::string & timecode, const std::string & last_timecode = "");
/**
for displaying characters on the strip LCD
pass SurfacePort so we know which sysex header to use
*/
MidiByteArray strip_display (Surface &, const Strip & strip, unsigned int line_number, const std::string & line);
/// blank the strip LCD, ie write all spaces. Pass Surface so we know which sysex header to use.
MidiByteArray strip_display_blank (Surface&, const Strip & strip, unsigned int line_number);
/// for generating all strip names. Pass SurfacePort so we know which sysex header to use.
MidiByteArray all_strips_display (SurfacePort &, std::vector<std::string> & lines1, std::vector<std::string> & lines2);
protected:
static MIDI::byte calculate_pot_value (midi_pot_mode mode, const ControlState &);
};
}
#endif

View file

@ -95,3 +95,4 @@ Meter::update_message (float dB)
return msg;
}

View file

@ -35,10 +35,10 @@ public:
, last_segment_value_sent (-1)
, overload_on (false) {}
virtual type_t type() const { return type_meter; }
MidiByteArray update_message (float dB);
MidiByteArray zero() { return update_message (-99999999.0); }
static Control* factory (Surface&, int id, const char*, Group&);
int last_segment_value_sent;

View file

@ -0,0 +1,87 @@
/*
Copyright (C) 2006,2007 John Anderson
Copyright (C) 2012 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
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cmath>
#include "pot.h"
#include "surface.h"
#include "control_group.h"
using namespace Mackie;
Control*
Pot::factory (Surface& surface, int id, const char* name, Group& group)
{
Pot* p = new Pot (id, name, group);
surface.pots[id] = p;
surface.controls.push_back (p);
group.add (*p);
return p;
}
MidiByteArray
Pot::set_mode (Pot::Mode m)
{
mode = m;
return update_message ();
}
MidiByteArray
Pot::set_onoff (bool onoff)
{
on = onoff;
return update_message ();
}
MidiByteArray
Pot::set_value (float normalized)
{
value = normalized;
return update_message ();
}
MidiByteArray
Pot::set_all (float val, bool onoff, Mode m)
{
value = val;
on = onoff;
mode = m;
return update_message ();
}
MidiByteArray
Pot::update_message ()
{
// TODO do an exact calc for 0.50? To allow manually re-centering the port.
// center on or off
MIDI::byte msg = (value > 0.45 && value < 0.55 ? 1 : 0) << 6;
// mode
msg |= (mode << 4);
// value, but only if off hasn't explicitly been set
if (on) {
msg += (lrintf (value * 10.0) + 1) & 0x0f; // 0b00001111
}
return MidiByteArray (3, 0xb0, raw_id(), msg);
}

View file

@ -2,25 +2,40 @@
#define __ardour_mackie_control_protocol_pot_h__
#include "controls.h"
#include "ledring.h"
namespace Mackie {
class Pot : public Control
{
public:
enum Mode {
dot = 0,
boost_cut = 1,
wrap = 2,
spread = 3,
};
Pot (int id, std::string name, Group & group)
: Control (id, name, group)
, _led_ring (id, name + "_ring", group) {}
, value (0.0)
, mode (dot)
, on (true) {}
virtual type_t type() const { return type_pot; }
MidiByteArray set_mode (Mode);
MidiByteArray set_value (float);
MidiByteArray set_onoff (bool);
MidiByteArray set_all (float, bool, Mode);
virtual const LedRing & led_ring() const {return _led_ring; }
MidiByteArray zero() { return set_value (0.0); }
MidiByteArray update_message ();
static Control* factory (Surface&, int id, const char*, Group&);
private:
LedRing _led_ring;
private:
float value;
Mode mode;
bool on;
};
}

View file

@ -40,7 +40,6 @@
#include "surface.h"
#include "button.h"
#include "led.h"
#include "ledring.h"
#include "pot.h"
#include "fader.h"
#include "jog.h"
@ -110,12 +109,8 @@ void Strip::add (Control & control)
_fader_touch = reinterpret_cast<Button*>(&control);
} else if (control.name() == "meter") {
_meter = reinterpret_cast<Meter*>(&control);
} else if (control.type() == Control::type_led || control.type() == Control::type_led_ring) {
// relax
} else {
ostringstream os;
os << "Strip::add: unknown control type " << control;
throw MackieControlException (os.str());
// relax
}
}
@ -308,27 +303,24 @@ Strip::notify_all()
void
Strip::notify_solo_changed ()
{
if (_route) {
Button& button = solo();
_surface->write (button.led().set_state (_route->soloed() ? on : off));
if (_route && _solo) {
_surface->write (_solo->led().set_state (_route->soloed() ? on : off));
}
}
void
Strip::notify_mute_changed ()
{
if (_route) {
Button & button = mute();
_surface->write (button.led().set_state (_route->muted() ? on : off));
if (_route && _mute) {
_surface->write (_mute->led().set_state (_route->muted() ? on : off));
}
}
void
Strip::notify_record_enable_changed ()
{
if (_route) {
Button & button = recenable();
_surface->write (button.led().set_state (_route->record_enabled() ? on : off));
if (_route && _recenable) {
_surface->write (_recenable->led().set_state (_route->record_enabled() ? on : off));
}
}
@ -350,19 +342,12 @@ Strip::notify_gain_changed (bool force_update)
if (_route) {
Fader & fader = gain();
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("route %1 gain change, update fader %2 on port %3 in-use ? %4\n",
_route->name(),
fader.raw_id(),
_surface->port().output_port().name(),
fader.in_use()));
if (!fader.in_use()) {
float gain_value = gain_to_slider_position (_route->gain_control()->get_value());
float position = gain_to_slider_position (_route->gain_control()->get_value());
// check that something has actually changed
if (force_update || gain_value != _last_gain_written) {
_surface->write (builder.build_fader (fader, gain_value));
_last_gain_written = gain_value;
} else {
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader not updated because gain still equals %1\n", gain_value));
if (force_update || position != _last_gain_written) {
_surface->write (fader.set_position (position));
_last_gain_written = position;
}
}
}
@ -385,8 +370,8 @@ Strip::notify_property_changed (const PropertyChange& what_changed)
line1 = PBD::short_version (fullname, 6);
}
_surface->write (builder.strip_display (*_surface, *this, 0, line1));
_surface->write (builder.strip_display_blank (*_surface, *this, 1));
_surface->write (display (0, line1));
_surface->write (blank_display (1));
}
}
@ -396,13 +381,16 @@ Strip::notify_panner_changed (bool force_update)
if (_route) {
Pot & pot = vpot();
boost::shared_ptr<Panner> panner = _route->panner();
if (panner) {
double pos = panner->position ();
// cache the MidiByteArray here, because the mackie led control is much lower
// resolution than the panner control. So we save lots of byte
// sends in spite of more work on the comparison
MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot);
MidiByteArray bytes = pot.set_all (pos, true, Pot::dot);
// check that something has actually changed
if (force_update || bytes != _last_pan_written)
{
@ -410,7 +398,7 @@ Strip::notify_panner_changed (bool force_update)
_last_pan_written = bytes;
}
} else {
_surface->write (builder.zero_control (pot));
_surface->write (pot.zero());
}
}
}
@ -418,10 +406,16 @@ Strip::notify_panner_changed (bool force_update)
bool
Strip::handle_button (SurfacePort & port, Control & control, ButtonState bs)
{
Button* button = dynamic_cast<Button*>(&control);
if (!button) {
return false;
}
if (!_route) {
// no route so always switch the light off
// because no signals will be emitted by a non-route
_surface->write (control.led().set_state (off));
_surface->write (button->led().set_state (off));
return false;
}
@ -500,3 +494,61 @@ Strip::update_meter ()
float dB = const_cast<PeakMeter&> (_route->peak_meter()).peak_power (0);
_surface->write (meter().update_message (dB));
}
MidiByteArray
Strip::zero ()
{
MidiByteArray retval;
for (Group::Controls::const_iterator it = _controls.begin(); it != _controls.end(); ++it) {
retval << (*it)->zero ();
}
retval << blank_display (0);
retval << blank_display (1);
return retval;
}
MidiByteArray
Strip::blank_display (uint32_t line_number)
{
return display (line_number, string());
}
MidiByteArray
Strip::display (uint32_t line_number, const std::string& line)
{
assert (line_number <= 1);
MidiByteArray retval;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip_display index: %1, line %2 = %3\n", _index, line_number, line));
// sysex header
retval << _surface->sysex_hdr();
// code for display
retval << 0x12;
// offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
retval << (_index * 7 + (line_number * 0x38));
// ascii data to display
retval << line;
// pad with " " out to 6 chars
for (int i = line.length(); i < 6; ++i) {
retval << ' ';
}
// column spacer, unless it's the right-hand column
if (_index < 7) {
retval << ' ';
}
// sysex trailer
retval << MIDI::eox;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display midi: %1\n", retval));
return retval;
}

View file

@ -5,9 +5,11 @@
#include <iostream>
#include "pbd/property_basics.h"
#include "pbd/signals.h"
#include "control_group.h"
#include "mackie_midi_builder.h"
#include "types.h"
#include "midi_byte_array.h"
namespace ARDOUR {
class Route;
@ -21,6 +23,7 @@ class Button;
class Pot;
class Fader;
class Meter;
class SurfacePort;
struct StripControlDefinition {
const char* name;
@ -78,6 +81,10 @@ public:
void periodic ();
MidiByteArray display (uint32_t line_number, const std::string&);
MidiByteArray blank_display (uint32_t line_number);
MidiByteArray zero ();
private:
Button* _solo;
Button* _recenable;
@ -91,8 +98,6 @@ private:
int _index;
Surface* _surface;
MackieMidiBuilder builder;
boost::shared_ptr<ARDOUR::Route> _route;
PBD::ScopedConnectionList route_connections;

View file

@ -17,14 +17,12 @@
#include "surface_port.h"
#include "surface.h"
#include "strip.h"
#include "mackie_midi_builder.h"
#include "mackie_control_protocol.h"
#include "mackie_jog_wheel.h"
#include "strip.h"
#include "button.h"
#include "led.h"
#include "ledring.h"
#include "pot.h"
#include "fader.h"
#include "jog.h"
@ -260,14 +258,6 @@ Surface::init_strips ()
}
}
void
Surface::display_timecode (const std::string & timecode, const std::string & timecode_last)
{
if (has_timecode_display()) {
_port->write (builder.timecode_display (*this, timecode, timecode_last));
}
}
float
Surface::scaled_delta (const ControlState & state, float current_speed)
{
@ -279,10 +269,10 @@ Surface::display_bank_start (uint32_t current_bank)
{
if (current_bank == 0) {
// send Ar. to 2-char display on the master
_port->write (builder.two_char_display ("Ar", ".."));
_port->write (two_char_display ("Ar", ".."));
} else {
// write the current first remote_id to the 2-char display
_port->write (builder.two_char_display (current_bank));
_port->write (two_char_display (current_bank));
}
}
@ -292,7 +282,10 @@ Surface::blank_jog_ring ()
Control* control = controls_by_name["jog"];
if (control) {
_port->write (builder.build_led_ring (*(dynamic_cast<Pot*> (control)), off));
Pot* pot = dynamic_cast<Pot*> (control);
if (pot) {
_port->write (pot->set_onoff (false));
}
}
}
@ -423,8 +416,11 @@ Surface::handle_control_event (Control & control, const ControlState & state)
// the state of the controls on the surface is usually updated
// from UI events.
switch (control.type()) {
case Control::type_fader:
Fader* fader = dynamic_cast<Fader*> (&control);
Button* button = dynamic_cast<Button*> (&control);
Pot* pot = dynamic_cast<Pot*> (&control);
if (fader) {
// find the route in the route table for the id
// if the route isn't available, skip it
// at which point the fader should just reset itself
@ -441,25 +437,25 @@ Surface::handle_control_event (Control & control, const ControlState & state)
// must echo bytes back to slider now, because
// the notifier only works if the fader is not being
// touched. Which it is if we're getting input.
_port->write (builder.build_fader ((Fader&)control, state.pos));
_port->write (fader->set_position (state.pos));
}
}
break;
case Control::type_button:
if (button) {
if (strip) {
strip->handle_button (*_port, control, state.button_state);
} else {
// handle all non-strip buttons
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.id()));
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.raw_id()));
_mcp.handle_button_event (*this, dynamic_cast<Button&>(control), state.button_state);
}
break;
}
// pot (jog wheel, external control)
case Control::type_pot:
if (pot) {
if (strip) {
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.id()));
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.raw_id()));
if (route) {
boost::shared_ptr<Panner> panner = route->panner_shell()->panner();
// pan for mono input routes, or stereo linked panners
@ -474,23 +470,21 @@ Surface::handle_control_event (Control & control, const ControlState & state)
}
} else {
// it's a pot for an umnapped route, so turn all the lights off
_port->write (builder.build_led_ring (dynamic_cast<Pot &> (control), off));
Pot* pot = dynamic_cast<Pot*> (&control);
if (pot) {
_port->write (pot->set_onoff (false));
}
}
} else {
if (control.is_jog()) {
JogWheel* wheel = dynamic_cast<JogWheel*> (pot);
if (wheel) {
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", state.ticks));
if (_jog_wheel) {
_jog_wheel->jog_event (*_port, control, state);
}
wheel->jog_event (*_port, control, state);
} else {
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("External controller moved %1\n", state.ticks));
cout << "external controller" << state.ticks * state.sign << endl;
}
}
break;
default:
break;
}
}
@ -550,7 +544,7 @@ Surface::zero_all ()
// zero all strips
for (Strips::iterator it = strips.begin(); it != strips.end(); ++it) {
_port->write (builder.zero_strip (*this, **it));
_port->write ((*it)->zero());
}
// turn off global buttons and leds
@ -560,13 +554,13 @@ Surface::zero_all ()
for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
Control & control = **it;
if (!control.group().is_strip() && control.accepts_feedback()) {
_port->write (builder.zero_control (control));
_port->write (control.zero());
}
}
// any hardware-specific stuff
// clear 2-char display
_port->write (builder.two_char_display ("LC"));
_port->write (two_char_display (" "));
// and the led ring for the master strip
blank_jog_ring ();
@ -578,6 +572,7 @@ Surface::periodic ()
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
(*s)->periodic ();
}
}
void
@ -591,22 +586,22 @@ Surface::jog_wheel_state_display (JogWheel::State state)
{
switch (state) {
case JogWheel::zoom:
_port->write (builder.two_char_display ("Zm"));
_port->write (two_char_display ("Zm"));
break;
case JogWheel::scroll:
_port->write (builder.two_char_display ("Sc"));
_port->write (two_char_display ("Sc"));
break;
case JogWheel::scrub:
_port->write (builder.two_char_display ("Sb"));
_port->write (two_char_display ("Sb"));
break;
case JogWheel::shuttle:
_port->write (builder.two_char_display ("Sh"));
_port->write (two_char_display ("Sh"));
break;
case JogWheel::speed:
_port->write (builder.two_char_display ("Sp"));
_port->write (two_char_display ("Sp"));
break;
case JogWheel::select:
_port->write (builder.two_char_display ("Se"));
_port->write (two_char_display ("Se"));
break;
}
}
@ -625,3 +620,90 @@ Surface::map_routes (const vector<boost::shared_ptr<Route> >& routes)
(*s)->set_route (*r);
}
}
static char translate_seven_segment (char achar)
{
achar = toupper (achar);
if (achar >= 0x40 && achar <= 0x60)
return achar - 0x40;
else if (achar >= 0x21 && achar <= 0x3f)
return achar;
else
return 0x00;
}
MidiByteArray
Surface::two_char_display (const std::string & msg, const std::string & dots)
{
if (msg.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: msg must be exactly 2 characters");
if (dots.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: dots must be exactly 2 characters");
MidiByteArray bytes (6, 0xb0, 0x4a, 0x00, 0xb0, 0x4b, 0x00);
// chars are understood by the surface in right-to-left order
// could also exchange the 0x4a and 0x4b, above
bytes[5] = translate_seven_segment (msg[0]) + (dots[0] == '.' ? 0x40 : 0x00);
bytes[2] = translate_seven_segment (msg[1]) + (dots[1] == '.' ? 0x40 : 0x00);
return bytes;
}
MidiByteArray
Surface::two_char_display (unsigned int value, const std::string & /*dots*/)
{
ostringstream os;
os << setfill('0') << setw(2) << value % 100;
return two_char_display (os.str());
}
void
Surface::display_timecode (const std::string & timecode, const std::string & timecode_last)
{
if (has_timecode_display()) {
_port->write (timecode_display (timecode, timecode_last));
}
}
MidiByteArray
Surface::timecode_display (const std::string & timecode, const std::string & last_timecode)
{
// if there's no change, send nothing, not even sysex header
if (timecode == last_timecode) return MidiByteArray();
// length sanity checking
string local_timecode = timecode;
// truncate to 10 characters
if (local_timecode.length() > 10) {
local_timecode = local_timecode.substr (0, 10);
}
// pad to 10 characters
while (local_timecode.length() < 10) {
local_timecode += " ";
}
// find the suffix of local_timecode that differs from last_timecode
std::pair<string::const_iterator,string::iterator> pp = mismatch (last_timecode.begin(), last_timecode.end(), local_timecode.begin());
MidiByteArray retval;
// sysex header
retval << sysex_hdr();
// code for timecode display
retval << 0x10;
// translate characters. These are sent in reverse order of display
// hence the reverse iterators
string::reverse_iterator rend = reverse_iterator<string::iterator> (pp.second);
for (string::reverse_iterator it = local_timecode.rbegin(); it != rend; ++it) {
retval << translate_seven_segment (*it);
}
// sysex trailer
retval << MIDI::eox;
return retval;
}

View file

@ -7,7 +7,6 @@
#include "controls.h"
#include "types.h"
#include "mackie_midi_builder.h"
#include "mackie_jog_wheel.h"
namespace MIDI {
@ -33,7 +32,6 @@ class Fader;
class Jog;
class Pot;
class Led;
class LedRing;
class Surface : public PBD::ScopedConnectionList
{
@ -132,6 +130,19 @@ public:
void handle_control_event (Mackie::Control & control, const Mackie::ControlState & state);
// display the first 2 chars of the msg in the 2 char display
// . is appended to the previous character, so A.B. would
// be two characters
MidiByteArray two_char_display (const std::string & msg, const std::string & dots = " ");
MidiByteArray two_char_display (unsigned int value, const std::string & dots = " ");
/**
Timecode display. Only the difference between timecode and last_timecode will
be encoded, to save midi bandwidth. If they're the same, an empty array will
be returned
*/
MidiByteArray timecode_display (const std::string & timecode, const std::string & last_timecode = "");
protected:
void init_controls();
void init_strips ();
@ -144,7 +155,6 @@ public:
bool _active;
bool _connected;
Mackie::JogWheel* _jog_wheel;
MackieMidiBuilder builder;
void jog_wheel_state_display (Mackie::JogWheel::State state);
};

View file

@ -106,7 +106,6 @@ class Strip;
class Group;
class Pot;
class Led;
class LedRing;
}

View file

@ -29,10 +29,10 @@ def build(bld):
led.cc
mackie_control_protocol.cc
mackie_jog_wheel.cc
mackie_midi_builder.cc
mcp_buttons.cc
meter.cc
midi_byte_array.cc
pot.cc
strip.cc
surface.cc
surface_port.cc