summaryrefslogtreecommitdiff
path: root/libs/surfaces/us2400
diff options
context:
space:
mode:
authorBen Loftis <ben@harrisonconsoles.com>2017-10-05 10:54:46 -0500
committerBen Loftis <ben@harrisonconsoles.com>2017-10-05 10:55:45 -0500
commit2107d094541dd836b46be0ff48964fd44fbbeacf (patch)
treec952c4f730568031f6669ff25921b1b1f3839d07 /libs/surfaces/us2400
parent32c725115d87752a01cae533070b9931867667cd (diff)
US2400: add us2400 files to repository.
Diffstat (limited to 'libs/surfaces/us2400')
-rw-r--r--libs/surfaces/us2400/TODO2
-rw-r--r--libs/surfaces/us2400/button.cc142
-rw-r--r--libs/surfaces/us2400/button.h121
-rw-r--r--libs/surfaces/us2400/control_group.h44
-rw-r--r--libs/surfaces/us2400/controls.cc127
-rw-r--r--libs/surfaces/us2400/controls.h95
-rw-r--r--libs/surfaces/us2400/device_info.cc366
-rw-r--r--libs/surfaces/us2400/device_info.h132
-rw-r--r--libs/surfaces/us2400/device_profile.cc329
-rw-r--r--libs/surfaces/us2400/device_profile.h80
-rw-r--r--libs/surfaces/us2400/fader.cc70
-rw-r--r--libs/surfaces/us2400/fader.h38
-rw-r--r--libs/surfaces/us2400/gui.cc824
-rw-r--r--libs/surfaces/us2400/gui.h142
-rw-r--r--libs/surfaces/us2400/interface.cc102
-rw-r--r--libs/surfaces/us2400/jog.cc37
-rw-r--r--libs/surfaces/us2400/jog.h49
-rw-r--r--libs/surfaces/us2400/jog_wheel.cc70
-rw-r--r--libs/surfaces/us2400/jog_wheel.h37
-rw-r--r--libs/surfaces/us2400/led.cc75
-rw-r--r--libs/surfaces/us2400/led.h66
-rw-r--r--libs/surfaces/us2400/mcp_buttons.cc1111
-rw-r--r--libs/surfaces/us2400/meter.cc137
-rw-r--r--libs/surfaces/us2400/meter.h66
-rw-r--r--libs/surfaces/us2400/midi_byte_array.cc96
-rw-r--r--libs/surfaces/us2400/midi_byte_array.h76
-rw-r--r--libs/surfaces/us2400/pot.cc86
-rw-r--r--libs/surfaces/us2400/pot.h64
-rw-r--r--libs/surfaces/us2400/strip.cc970
-rw-r--r--libs/surfaces/us2400/strip.h158
-rw-r--r--libs/surfaces/us2400/surface.cc1090
-rw-r--r--libs/surfaces/us2400/surface.h204
-rw-r--r--libs/surfaces/us2400/surface_port.cc201
-rw-r--r--libs/surfaces/us2400/surface_port.h91
-rw-r--r--libs/surfaces/us2400/test.cc25
-rw-r--r--libs/surfaces/us2400/timer.h104
-rw-r--r--libs/surfaces/us2400/types.cc51
-rw-r--r--libs/surfaces/us2400/types.h115
-rw-r--r--libs/surfaces/us2400/us2400_control_exception.h48
-rw-r--r--libs/surfaces/us2400/us2400_control_protocol.cc1941
-rw-r--r--libs/surfaces/us2400/us2400_control_protocol.h480
-rw-r--r--libs/surfaces/us2400/us2400_control_protocol_poll.cc26
-rw-r--r--libs/surfaces/us2400/wscript49
43 files changed, 10137 insertions, 0 deletions
diff --git a/libs/surfaces/us2400/TODO b/libs/surfaces/us2400/TODO
new file mode 100644
index 0000000000..b488929c52
--- /dev/null
+++ b/libs/surfaces/us2400/TODO
@@ -0,0 +1,2 @@
+
+MB: allow control of strip params when a mix-bus or master is selected
diff --git a/libs/surfaces/us2400/button.cc b/libs/surfaces/us2400/button.cc
new file mode 100644
index 0000000000..9f9448688e
--- /dev/null
+++ b/libs/surfaces/us2400/button.cc
@@ -0,0 +1,142 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <glib.h>
+
+#include "ardour/ardour.h"
+
+#include "button.h"
+#include "surface.h"
+#include "control_group.h"
+
+using namespace ArdourSurface;
+using namespace US2400;
+
+Control*
+Button::factory (Surface& surface, Button::ID bid, int id, const std::string& name, Group& group)
+{
+ Button* b = new Button (surface, bid, id, name, group);
+ /* store button with the device-specific ID */
+ surface.buttons[id] = b;
+ surface.controls.push_back (b);
+ group.add (*b);
+ return b;
+}
+
+void
+Button::pressed ()
+{
+ press_time = ARDOUR::get_microseconds ();
+}
+
+void
+Button::released ()
+{
+ press_time = 0;
+}
+
+int32_t
+Button::long_press_count ()
+{
+ if (press_time == 0) {
+ return -1; /* button is not pressed */
+ }
+
+ const ARDOUR::microseconds_t delta = ARDOUR::get_microseconds () - press_time;
+
+ if (delta < 500000) {
+ return 0;
+ } else if (delta < 1000000) {
+ return 1;
+ }
+
+ return 2;
+}
+int
+Button::name_to_id (const std::string& name)
+{
+ if (!g_ascii_strcasecmp (name.c_str(), "Send")) { return Send; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Pan")) { return Pan; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Bank Left")) { return Left; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Bank Right")) { return Right; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Flip")) { return Flip; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F1")) { return F1; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F2")) { return F2; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F3")) { return F3; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F4")) { return F4; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F5")) { return F5; }
+ if (!g_ascii_strcasecmp (name.c_str(), "F6")) { return F6; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Shift")) { return Shift; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Drop")) { return Drop; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Clear Solo")) { return ClearSolo; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Rewind")) { return Rewind; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Ffwd")) { return Ffwd; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Stop")) { return Stop; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Play")) { return Play; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Record")) { return Record; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Scrub")) { return Scrub; }
+
+ /* Strip buttons */
+
+ if (!g_ascii_strcasecmp (name.c_str(), "Solo")) { return Solo; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Mute")) { return Mute; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Select")) { return Select; }
+ if (!g_ascii_strcasecmp (name.c_str(), "Fader Touch")) { return FaderTouch; }
+
+ /* Master Fader button */
+
+ if (!g_ascii_strcasecmp (name.c_str(), "Master Fader Touch")) { return MasterFaderTouch; }
+
+ return -1;
+}
+
+std::string
+Button::id_to_name (Button::ID id)
+{
+ if (id == Send) { return "Send"; }
+ if (id == Pan) { return "Pan"; }
+ if (id == Left) { return "Bank Left"; }
+ if (id == Right) { return "Bank Right"; }
+ if (id == Flip) { return "Flip"; }
+ if (id == F1) { return "F1"; }
+ if (id == F2) { return "F2"; }
+ if (id == F3) { return "F3"; }
+ if (id == F4) { return "F4"; }
+ if (id == F5) { return "F5"; }
+ if (id == F6) { return "F6"; }
+ if (id == Shift) { return "Shift"; }
+ if (id == Drop) { return "Drop"; }
+ if (id == ClearSolo) { return "Clear Solo"; }
+ if (id == Rewind) { return "Rewind"; }
+ if (id == Ffwd) { return "FFwd"; }
+ if (id == Stop) { return "Stop"; }
+ if (id == Play) { return "Play"; }
+ if (id == Record) { return "Record"; }
+ if (id == Scrub) { return "Scrub"; }
+
+ if (id == Solo) { return "Solo"; }
+ if (id == Mute) { return "Mute"; }
+ if (id == Select) { return "Select"; }
+ if (id == FaderTouch) { return "Fader Touch"; }
+
+ if (id == MasterFaderTouch) { return "Master Fader Touch"; }
+
+ return "???";
+}
diff --git a/libs/surfaces/us2400/button.h b/libs/surfaces/us2400/button.h
new file mode 100644
index 0000000000..4f5a306844
--- /dev/null
+++ b/libs/surfaces/us2400/button.h
@@ -0,0 +1,121 @@
+/*
+ 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_us2400_control_protocol_button_h__
+#define __ardour_us2400_control_protocol_button_h__
+
+#include "ardour/types.h"
+
+#include "controls.h"
+#include "led.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Surface;
+
+class Button : public Control
+{
+public:
+/* These values uniquely identify each possible button that an MCP device may
+ send. Each DeviceInfo object contains its own set of button definitions that
+ define what device ID will be sent for each button, and there is no reason
+ for them to be the same. */
+
+ enum ID {
+ /* Global Buttons */
+
+ Scrub,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ Rewind,
+ Ffwd,
+ Stop,
+ Play,
+ Record,
+ Left,
+ Right,
+ Flip,
+
+ FinalGlobalButton,
+
+
+ /* Global buttons that users should not redefine */
+
+ Drop,
+ Send,
+ Pan,
+ ClearSolo,
+ Shift,
+ Option,
+ Ctrl,
+ CmdAlt,
+
+ /* Strip buttons */
+
+ Solo,
+ Mute,
+ Select,
+ FaderTouch,
+
+ /* Master fader */
+
+ MasterFaderTouch,
+ };
+
+
+ Button (Surface& s, ID bid, int did, std::string name, Group & group)
+ : Control (did, name, group)
+ , _surface (s)
+ , _bid (bid)
+ , _led (did, name + "_led", group)
+ , press_time (0) {}
+
+ MidiByteArray zero() { return _led.zero (); }
+ MidiByteArray set_state (LedState ls) { return _led.set_state (ls); }
+
+ ID bid() const { return _bid; }
+
+ static Control* factory (Surface& surface, Button::ID bid, int id, const std::string&, Group& group);
+ static int name_to_id (const std::string& name);
+ static std::string id_to_name (Button::ID);
+
+ Surface& surface() const { return _surface; }
+
+ void pressed ();
+ void released ();
+
+ int32_t long_press_count ();
+
+private:
+ Surface& _surface;
+ ID _bid; /* device independent button ID */
+ Led _led;
+ ARDOUR::microseconds_t press_time;
+};
+
+} // US2400 namespace
+} // ArdourSurface namespace
+
+#endif
diff --git a/libs/surfaces/us2400/control_group.h b/libs/surfaces/us2400/control_group.h
new file mode 100644
index 0000000000..52037b691a
--- /dev/null
+++ b/libs/surfaces/us2400/control_group.h
@@ -0,0 +1,44 @@
+#ifndef __ardour_us2400_control_protocol_control_group_h__
+#define __ardour_us2400_control_protocol_control_group_h__
+
+#include <vector>
+
+namespace ArdourSurface {
+namespace US2400 {
+
+class Control;
+
+/**
+ This is a loose group of controls, eg cursor buttons,
+ transport buttons, functions buttons etc.
+*/
+class Group
+{
+public:
+ Group (const std::string & name)
+ : _name (name) {}
+
+ virtual ~Group() {}
+
+ virtual bool is_strip() const { return false; }
+ virtual bool is_master() const { return false; }
+
+ virtual void add (Control & control);
+
+ const std::string & name() const { return _name; }
+ void set_name (const std::string & rhs) { _name = rhs; }
+
+ typedef std::vector<Control*> Controls;
+ const Controls & controls() const { return _controls; }
+
+protected:
+ Controls _controls;
+
+private:
+ std::string _name;
+};
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/controls.cc b/libs/surfaces/us2400/controls.cc
new file mode 100644
index 0000000000..a342bbf988
--- /dev/null
+++ b/libs/surfaces/us2400/controls.cc
@@ -0,0 +1,127 @@
+ /*
+ 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 <iostream>
+#include <iomanip>
+#include <sstream>
+
+#include "ardour/automation_control.h"
+#include "pbd/enumwriter.h"
+
+#include "controls.h"
+#include "types.h"
+#include "surface.h"
+#include "control_group.h"
+#include "button.h"
+#include "led.h"
+#include "pot.h"
+#include "fader.h"
+#include "jog.h"
+#include "meter.h"
+
+
+using namespace std;
+using namespace ArdourSurface;
+using namespace US2400;
+
+using ARDOUR::AutomationControl;
+
+void Group::add (Control& control)
+{
+ _controls.push_back (&control);
+}
+
+Control::Control (int id, std::string name, Group & group)
+ : _id (id)
+ , _name (name)
+ , _group (group)
+ , _in_use (false)
+{
+}
+
+/** @return true if the control is in use, or false otherwise.
+ Buttons are `in use' when they are held down.
+ Faders with touch support are `in use' when they are being touched.
+ Pots, or faders without touch support, are `in use' from the first move
+ event until a timeout after the last move event.
+*/
+bool
+Control::in_use () const
+{
+ return _in_use;
+}
+
+void
+Control::set_in_use (bool in_use)
+{
+ _in_use = in_use;
+}
+
+void
+Control::set_control (boost::shared_ptr<AutomationControl> ac)
+{
+ normal_ac = ac;
+}
+
+void
+Control::set_value (float val, PBD::Controllable::GroupControlDisposition group_override)
+{
+ if (normal_ac) {
+ normal_ac->set_value (normal_ac->interface_to_internal (val), group_override);
+ }
+}
+
+float
+Control::get_value ()
+{
+ if (!normal_ac) {
+ return 0.0f;
+ }
+ return normal_ac->internal_to_interface (normal_ac->get_value());
+}
+
+void
+Control::start_touch (double when)
+{
+ if (normal_ac) {
+ return normal_ac->start_touch (when);
+ }
+}
+
+void
+Control::stop_touch (double when)
+{
+ if (normal_ac) {
+ return normal_ac->stop_touch (when);
+ }
+}
+
+ostream & operator << (ostream & os, const ArdourSurface::US2400::Control & control)
+{
+ os << typeid (control).name();
+ os << " { ";
+ os << "name: " << control.name();
+ os << ", ";
+ os << "id: " << "0x" << setw(2) << setfill('0') << hex << control.id() << setfill(' ');
+ os << ", ";
+ os << "group: " << control.group().name();
+ os << " }";
+
+ return os;
+}
+
diff --git a/libs/surfaces/us2400/controls.h b/libs/surfaces/us2400/controls.h
new file mode 100644
index 0000000000..11e27b2ac5
--- /dev/null
+++ b/libs/surfaces/us2400/controls.h
@@ -0,0 +1,95 @@
+/*
+ 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 __us2400_controls_h__
+#define __us2400_controls_h__
+
+#include <map>
+#include <vector>
+#include <string>
+#include <stdint.h>
+
+#include <boost/smart_ptr.hpp>
+
+#include "pbd/controllable.h"
+#include "pbd/signals.h"
+
+#include "us2400_control_exception.h"
+#include "midi_byte_array.h"
+
+namespace ARDOUR {
+ class AutomationControl;
+}
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Strip;
+class Group;
+class Surface;
+
+class Control {
+public:
+ Control (int id, std::string name, Group& group);
+ virtual ~Control() {}
+
+ int id() const { return _id; }
+ const std::string & name() const { return _name; }
+ Group & group() const { return _group; }
+
+ 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.
+ */
+ Control* in_use_touch_control;
+
+ boost::shared_ptr<ARDOUR::AutomationControl> control () const { return normal_ac; }
+ virtual void set_control (boost::shared_ptr<ARDOUR::AutomationControl>);
+ virtual void reset_control () { normal_ac.reset(); }
+
+ float get_value ();
+ void set_value (float val, PBD::Controllable::GroupControlDisposition gcd = PBD::Controllable::UseGroup);
+
+ virtual void start_touch (double when);
+ virtual void stop_touch (double when);
+
+ protected:
+ boost::shared_ptr<ARDOUR::AutomationControl> normal_ac;
+
+ private:
+ int _id; /* possibly device-dependent ID */
+ std::string _name;
+ Group& _group;
+ bool _in_use;
+};
+
+}
+}
+
+std::ostream & operator << (std::ostream & os, const ArdourSurface::US2400::Control & control);
+
+#endif /* __us2400_controls_h__ */
diff --git a/libs/surfaces/us2400/device_info.cc b/libs/surfaces/us2400/device_info.cc
new file mode 100644
index 0000000000..784c114e55
--- /dev/null
+++ b/libs/surfaces/us2400/device_info.cc
@@ -0,0 +1,366 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <cstdlib>
+#include <cstring>
+#include <glibmm/miscutils.h>
+
+#include "pbd/xml++.h"
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "pbd/convert.h"
+#include "pbd/stl_delete.h"
+
+#include "ardour/filesystem_paths.h"
+
+#include "device_info.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace US2400;
+
+using std::string;
+using std::vector;
+
+std::map<std::string,DeviceInfo> DeviceInfo::device_info;
+
+DeviceInfo::DeviceInfo()
+ : _strip_cnt (8)
+ , _extenders (3)
+ , _master_position (0)
+ , _has_two_character_display (false)
+ , _has_master_fader (true)
+ , _has_timecode_display (false)
+ , _has_global_controls (true)
+ , _has_jog_wheel (true)
+ , _has_touch_sense_faders (true)
+ , _uses_logic_control_buttons (false)
+ , _no_handshake (false)
+ , _has_meters (true)
+ , _has_separate_meters (true)
+ , _device_type (MCU)
+ , _name (X_("US2400"))
+{
+ us2400_control_buttons ();
+}
+
+DeviceInfo::~DeviceInfo()
+{
+}
+
+GlobalButtonInfo&
+DeviceInfo::get_global_button(Button::ID id)
+{
+ GlobalButtonsInfo::iterator it;
+
+ it = _global_buttons.find (id);
+
+ return it->second;
+}
+
+std::string&
+DeviceInfo::get_global_button_name(Button::ID id)
+{
+ GlobalButtonsInfo::iterator it;
+
+ it = _global_buttons.find (id);
+ if (it == _global_buttons.end ()) {
+ _global_button_name = "";
+ return _global_button_name;
+ } else {
+ return it->second.label;
+ }
+}
+
+void
+DeviceInfo::us2400_control_buttons ()
+{
+ _global_buttons.clear ();
+ shared_buttons ();
+}
+
+void
+DeviceInfo::logic_control_buttons ()
+{
+ _global_buttons.clear ();
+ shared_buttons ();
+}
+
+void
+DeviceInfo::shared_buttons ()
+{
+// US-2499 button notes:
+// CHAN button sends nothing. it inititates a dumb 0..127 knob mode for the 24 knobs
+// PAN sends the regular pan/surround message. this tells our strips to send the pan knob position
+// AUX1-6 all send the same 0x29 + 0x21 message, I believe the surface uses this to captures knob info, somehow
+
+ _global_buttons[Button::Pan] = GlobalButtonInfo ("Pan/Surround", "assignment", 0x2a); // US-2400: this is sent (on&off in one msg) from the Pan button
+
+ _global_buttons[Button::Left] = GlobalButtonInfo ("Bank Left", "bank", 0x2e);
+ _global_buttons[Button::Right] = GlobalButtonInfo ("Bank Right", "bank", 0x2f);
+
+ _global_buttons[Button::Flip] = GlobalButtonInfo ("Flip", "assignment", 0x32);
+
+ _global_buttons[Button::F1] = GlobalButtonInfo ("F1", "function select", 0x36);
+ _global_buttons[Button::F2] = GlobalButtonInfo ("F2", "function select", 0x37);
+ _global_buttons[Button::F3] = GlobalButtonInfo ("F3", "function select", 0x38);
+ _global_buttons[Button::F4] = GlobalButtonInfo ("F4", "function select", 0x39);
+ _global_buttons[Button::F5] = GlobalButtonInfo ("F5", "function select", 0x3a);
+ _global_buttons[Button::F6] = GlobalButtonInfo ("F6", "function select", 0x3b);
+
+
+ _global_buttons[Button::Shift] = GlobalButtonInfo ("Shift", "modifiers", 0x46);
+ _global_buttons[Button::Option] = GlobalButtonInfo ("Option", "modifiers", 0x47); //There is no physical Option button, but US2400 sends Option+ track Solo == solo clear
+
+ _global_buttons[Button::Drop] = GlobalButtonInfo ("Drop", "transport", 0x57); // US-2400: combined with ffwd/rew to call IN/OUT
+
+ _global_buttons[Button::Rewind] = GlobalButtonInfo ("Rewind", "transport", 0x5b); // US-2400: if "Drop" 0x57 is held, this is IN
+ _global_buttons[Button::Ffwd] = GlobalButtonInfo ("Fast Fwd", "transport", 0x5c); // US-2400: if "Drop 0x57 is held, this is OUT
+ _global_buttons[Button::Stop] = GlobalButtonInfo ("Stop", "transport", 0x5d);
+ _global_buttons[Button::Play] = GlobalButtonInfo ("Play", "transport", 0x5e);
+ _global_buttons[Button::Record] = GlobalButtonInfo ("Record", "transport", 0x5f);
+
+ _global_buttons[Button::Scrub] = GlobalButtonInfo ("Scrub", "cursor", 0x65);
+
+ _strip_buttons[Button::Solo] = StripButtonInfo (0x08, "Solo"); //combined with Option" to do solo clear
+ _strip_buttons[Button::Mute] = StripButtonInfo (0x10, "Mute");
+ _strip_buttons[Button::Select] = StripButtonInfo (0x18, "Select");
+
+ _strip_buttons[Button::FaderTouch] = StripButtonInfo (0x68, "Fader Touch");
+
+ _global_buttons[Button::MasterFaderTouch] = GlobalButtonInfo ("Master Fader Touch", "master", 0x70);
+}
+
+int
+DeviceInfo::set_state (const XMLNode& node, int /* version */)
+{
+ const XMLProperty* prop;
+ const XMLNode* child;
+
+ if (node.name() != "US-2400Device") {
+ return -1;
+ }
+
+ if ((child = node.child ("LogicControlButtons")) != 0) {
+ if (child->get_property ("value", _uses_logic_control_buttons)) {
+ if (_uses_logic_control_buttons) {
+ logic_control_buttons ();
+ } else {
+ us2400_control_buttons ();
+ }
+ }
+ }
+
+ if ((child = node.child ("Buttons")) != 0) {
+ XMLNodeConstIterator i;
+ const XMLNodeList& nlist (child->children());
+
+ std::string name;
+ for (i = nlist.begin(); i != nlist.end(); ++i) {
+ if ((*i)->name () == "GlobalButton") {
+ if ((*i)->get_property ("name", name)) {
+ int id = Button::name_to_id (name);
+ if (id >= 0) {
+ Button::ID bid = (Button::ID)id;
+ int32_t id;
+ if ((*i)->get_property ("id", id)) {
+ std::map<Button::ID, GlobalButtonInfo>::iterator b = _global_buttons.find (bid);
+ if (b != _global_buttons.end ()) {
+ b->second.id = id;
+ (*i)->get_property ("label", b->second.label);
+ }
+ }
+ }
+ }
+ } else if ((*i)->name () == "StripButton") {
+ if ((*i)->get_property ("name", name)) {
+ int id = Button::name_to_id (name);
+ if (id >= 0) {
+ Button::ID bid = (Button::ID)id;
+ int32_t base_id;
+ if ((*i)->get_property ("baseid", base_id)) {
+ std::map<Button::ID, StripButtonInfo>::iterator b = _strip_buttons.find (bid);
+ if (b != _strip_buttons.end ()) {
+ b->second.base_id = base_id;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+const string&
+DeviceInfo::name() const
+{
+ return _name;
+}
+
+uint32_t
+DeviceInfo::strip_cnt() const
+{
+ return _strip_cnt;
+}
+
+uint32_t
+DeviceInfo::extenders() const
+{
+ return _extenders;
+}
+
+uint32_t
+DeviceInfo::master_position() const
+{
+ return _master_position;
+}
+
+bool
+DeviceInfo::has_master_fader() const
+{
+ return _has_master_fader;
+}
+
+bool
+DeviceInfo::has_meters() const
+{
+ return _has_meters;
+}
+
+bool
+DeviceInfo::has_separate_meters() const
+{
+ return _has_separate_meters;
+}
+
+bool
+DeviceInfo::has_two_character_display() const
+{
+ return _has_two_character_display;
+}
+
+bool
+DeviceInfo::has_timecode_display () const
+{
+ return _has_timecode_display;
+}
+
+bool
+DeviceInfo::has_global_controls () const
+{
+ return _has_global_controls;
+}
+
+bool
+DeviceInfo::has_jog_wheel () const
+{
+ return _has_jog_wheel;
+}
+
+bool
+DeviceInfo::no_handshake () const
+{
+ return _no_handshake;
+}
+
+bool
+DeviceInfo::has_touch_sense_faders () const
+{
+ return _has_touch_sense_faders;
+}
+
+static const char * const devinfo_env_variable_name = "ARDOUR_MCP_PATH";
+static const char* const devinfo_dir_name = "mcp";
+static const char* const devinfo_suffix = ".device";
+
+static Searchpath
+devinfo_search_path ()
+{
+ bool devinfo_path_defined = false;
+ std::string spath_env (Glib::getenv (devinfo_env_variable_name, devinfo_path_defined));
+
+ if (devinfo_path_defined) {
+ return spath_env;
+ }
+
+ Searchpath spath (ardour_data_search_path());
+ spath.add_subdirectory_to_paths(devinfo_dir_name);
+
+ return spath;
+}
+
+static bool
+devinfo_filter (const string &str, void* /*arg*/)
+{
+ return (str.length() > strlen(devinfo_suffix) &&
+ str.find (devinfo_suffix) == (str.length() - strlen (devinfo_suffix)));
+}
+
+void
+DeviceInfo::reload_device_info ()
+{
+ vector<string> s;
+ vector<string> devinfos;
+ Searchpath spath (devinfo_search_path());
+
+ find_files_matching_filter (devinfos, spath, devinfo_filter, 0, false, true);
+ device_info.clear ();
+
+ if (devinfos.empty()) {
+ error << "No MCP device info files found using " << spath.to_string() << endmsg;
+ std::cerr << "No MCP device info files found using " << spath.to_string() << std::endl;
+ return;
+ }
+
+ for (vector<string>::iterator i = devinfos.begin(); i != devinfos.end(); ++i) {
+ string fullpath = *i;
+ DeviceInfo di; // has to be initial every loop or info from last added.
+
+ XMLTree tree;
+
+ if (!tree.read (fullpath.c_str())) {
+ continue;
+ }
+
+ XMLNode* root = tree.root ();
+ if (!root) {
+ continue;
+ }
+
+ if (di.set_state (*root, 3000) == 0) { /* version is ignored for now */
+ device_info[di.name()] = di;
+ }
+ }
+}
+
+std::ostream& operator<< (std::ostream& os, const US2400::DeviceInfo& di)
+{
+ os << di.name() << ' '
+ << di.strip_cnt() << ' '
+ << di.extenders() << ' '
+ << di.master_position() << ' '
+ ;
+ return os;
+}
diff --git a/libs/surfaces/us2400/device_info.h b/libs/surfaces/us2400/device_info.h
new file mode 100644
index 0000000000..3d8d4478f5
--- /dev/null
+++ b/libs/surfaces/us2400/device_info.h
@@ -0,0 +1,132 @@
+/*
+ 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_us2400_control_protocol_device_info_h__
+#define __ardour_us2400_control_protocol_device_info_h__
+
+#include <iostream>
+#include <stdint.h>
+#include <string>
+#include <map>
+
+#include "button.h"
+
+class XMLNode;
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+struct GlobalButtonInfo {
+ std::string label; // visible to user
+ std::string group; // in case we want to present in a GUI
+ int32_t id; // value sent by device
+
+ GlobalButtonInfo () : id (-1) {}
+ GlobalButtonInfo (const std::string& l, const std::string& g, uint32_t i)
+ : label (l), group (g), id (i) {}
+};
+
+struct StripButtonInfo {
+ int32_t base_id;
+ std::string name;
+
+ StripButtonInfo () : base_id (-1) {}
+ StripButtonInfo (uint32_t i, const std::string& n)
+ : base_id (i), name (n) {}
+};
+
+class DeviceInfo
+{
+ public:
+ enum DeviceType {
+ MCU = 0x14,
+ MCXT = 0x15,
+ LC = 0x10,
+ LCXT = 0x11,
+ HUI = 0x5
+ };
+
+ DeviceInfo();
+ ~DeviceInfo();
+
+ int set_state (const XMLNode&, int version);
+
+ DeviceType device_type() const { return _device_type; }
+ uint32_t strip_cnt () const;
+ uint32_t extenders() const;
+ uint32_t master_position() const;
+ bool has_two_character_display() const;
+ bool has_master_fader () const;
+ bool has_timecode_display() const;
+ bool has_global_controls() const;
+ bool has_jog_wheel () const;
+ bool has_touch_sense_faders() const;
+ bool no_handshake() const;
+ bool has_meters() const;
+ bool has_separate_meters() const;
+ bool us2400() const { return _us2400; }
+ const std::string& name() const;
+
+ static std::map<std::string,DeviceInfo> device_info;
+ static void reload_device_info();
+
+ std::string& get_global_button_name(Button::ID);
+ GlobalButtonInfo& get_global_button(Button::ID);
+
+ typedef std::map<Button::ID,GlobalButtonInfo> GlobalButtonsInfo;
+ typedef std::map<Button::ID,StripButtonInfo> StripButtonsInfo;
+
+ const GlobalButtonsInfo& global_buttons() const { return _global_buttons; }
+ const StripButtonsInfo& strip_buttons() const { return _strip_buttons; }
+
+ private:
+ uint32_t _strip_cnt;
+ uint32_t _extenders;
+ uint32_t _master_position;
+ bool _has_two_character_display;
+ bool _has_master_fader;
+ bool _has_timecode_display;
+ bool _has_global_controls;
+ bool _has_jog_wheel;
+ bool _has_touch_sense_faders;
+ bool _uses_logic_control_buttons;
+ bool _no_handshake;
+ bool _has_meters;
+ bool _has_separate_meters;
+ bool _us2400;
+ DeviceType _device_type;
+ std::string _name;
+ std::string _global_button_name;
+
+ GlobalButtonsInfo _global_buttons;
+ StripButtonsInfo _strip_buttons;
+
+ void logic_control_buttons ();
+ void us2400_control_buttons ();
+ void shared_buttons ();
+};
+
+
+} // US2400 namespace
+} // ArdourSurface namespace
+
+std::ostream& operator<< (std::ostream& os, const ArdourSurface::US2400::DeviceInfo& di);
+
+#endif /* __ardour_us2400_control_protocol_device_info_h__ */
diff --git a/libs/surfaces/us2400/device_profile.cc b/libs/surfaces/us2400/device_profile.cc
new file mode 100644
index 0000000000..7af4deead5
--- /dev/null
+++ b/libs/surfaces/us2400/device_profile.cc
@@ -0,0 +1,329 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <glibmm/miscutils.h>
+
+#include "pbd/xml++.h"
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "pbd/stl_delete.h"
+#include "pbd/replace_all.h"
+
+#include "ardour/filesystem_paths.h"
+
+#include "us2400_control_protocol.h"
+#include "device_profile.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace US2400;
+
+using std::string;
+using std::vector;
+
+std::map<std::string,DeviceProfile> DeviceProfile::device_profiles;
+const std::string DeviceProfile::edited_indicator (" (edited)");
+const std::string DeviceProfile::default_profile_name ("User");
+
+DeviceProfile::DeviceProfile (const string& n)
+ : _name (n)
+ , edited (false)
+{
+}
+
+DeviceProfile::~DeviceProfile()
+{
+}
+
+static const char * const devprofile_env_variable_name = "ARDOUR_MCP_PATH";
+static const char* const devprofile_dir_name = "us2400";
+static const char* const devprofile_suffix = ".profile";
+
+static Searchpath
+devprofile_search_path ()
+{
+ bool devprofile_path_defined = false;
+ std::string spath_env (Glib::getenv (devprofile_env_variable_name, devprofile_path_defined));
+
+ if (devprofile_path_defined) {
+ return spath_env;
+ }
+
+ Searchpath spath (ardour_data_search_path());
+ spath.add_subdirectory_to_paths(devprofile_dir_name);
+
+ return spath;
+}
+
+static std::string
+user_devprofile_directory ()
+{
+ return Glib::build_filename (user_config_directory(), devprofile_dir_name);
+}
+
+static bool
+devprofile_filter (const string &str, void* /*arg*/)
+{
+ return (str.length() > strlen(devprofile_suffix) &&
+ str.find (devprofile_suffix) == (str.length() - strlen (devprofile_suffix)));
+}
+
+void
+DeviceProfile::reload_device_profiles ()
+{
+ vector<string> s;
+ vector<string> devprofiles;
+ Searchpath spath (devprofile_search_path());
+
+ find_files_matching_filter (devprofiles, spath, devprofile_filter, 0, false, true);
+ device_profiles.clear ();
+
+ if (devprofiles.empty()) {
+ error << "No MCP device info files found using " << spath.to_string() << endmsg;
+ return;
+ }
+
+ for (vector<string>::iterator i = devprofiles.begin(); i != devprofiles.end(); ++i) {
+ string fullpath = *i;
+ DeviceProfile dp; // has to be initial every loop or info from last added.
+
+ XMLTree tree;
+
+ if (!tree.read (fullpath.c_str())) {
+ continue;
+ }
+
+ XMLNode* root = tree.root ();
+ if (!root) {
+ continue;
+ }
+
+ if (dp.set_state (*root, 3000) == 0) { /* version is ignored for now */
+ dp.set_path (fullpath);
+ device_profiles[dp.name()] = dp;
+ }
+ }
+}
+
+int
+DeviceProfile::set_state (const XMLNode& node, int /* version */)
+{
+ const XMLProperty* prop;
+ const XMLNode* child;
+
+ if (node.name() != "US2400DeviceProfile") {
+ return -1;
+ }
+
+ /* name is mandatory */
+
+ if ((child = node.child ("Name")) == 0 || (prop = child->property ("value")) == 0) {
+ return -1;
+ } else {
+ _name = prop->value();
+ }
+
+ if ((child = node.child ("Buttons")) != 0) {
+ XMLNodeConstIterator i;
+ const XMLNodeList& nlist (child->children());
+
+ for (i = nlist.begin(); i != nlist.end(); ++i) {
+
+ if ((*i)->name() == "Button") {
+
+ if ((prop = (*i)->property ("name")) == 0) {
+ error << string_compose ("Button without name in device profile \"%1\" - ignored", _name) << endmsg;
+ continue;
+ }
+
+ int id = Button::name_to_id (prop->value());
+ if (id < 0) {
+ error << string_compose ("Unknown button ID \"%1\"", prop->value()) << endmsg;
+ continue;
+ }
+
+ Button::ID bid = (Button::ID) id;
+
+ ButtonActionMap::iterator b = _button_map.find (bid);
+
+ if (b == _button_map.end()) {
+ b = _button_map.insert (_button_map.end(), std::pair<Button::ID,ButtonActions> (bid, ButtonActions()));
+ }
+
+ (*i)->get_property ("plain", b->second.plain);
+ (*i)->get_property ("shift", b->second.shift);
+ }
+ }
+ }
+
+ edited = false;
+
+ return 0;
+}
+
+XMLNode&
+DeviceProfile::get_state () const
+{
+ XMLNode* node = new XMLNode ("US2400DeviceProfile");
+ XMLNode* child = new XMLNode ("Name");
+
+ child->set_property ("value", name());
+ node->add_child_nocopy (*child);
+
+ if (_button_map.empty()) {
+ return *node;
+ }
+
+ XMLNode* buttons = new XMLNode ("Buttons");
+ node->add_child_nocopy (*buttons);
+
+ for (ButtonActionMap::const_iterator b = _button_map.begin(); b != _button_map.end(); ++b) {
+ XMLNode* n = new XMLNode ("Button");
+
+ n->set_property ("name", Button::id_to_name (b->first));
+
+ if (!b->second.plain.empty()) {
+ n->set_property ("plain", b->second.plain);
+ }
+ if (!b->second.shift.empty()) {
+ n->set_property ("shift", b->second.shift);
+ }
+
+ buttons->add_child_nocopy (*n);
+ }
+
+ return *node;
+}
+
+string
+DeviceProfile::get_button_action (Button::ID id, int modifier_state) const
+{
+ ButtonActionMap::const_iterator i = _button_map.find (id);
+
+ if (i == _button_map.end()) {
+ return string();
+ }
+
+ if (modifier_state == US2400Protocol::MODIFIER_SHIFT) {
+ return i->second.shift;
+ }
+
+ return i->second.plain;
+}
+
+void
+DeviceProfile::set_button_action (Button::ID id, int modifier_state, const string& act)
+{
+ ButtonActionMap::iterator i = _button_map.find (id);
+
+ if (i == _button_map.end()) {
+ i = _button_map.insert (std::make_pair (id, ButtonActions())).first;
+ }
+
+ string action (act);
+ replace_all (action, "<Actions>/", "");
+
+ if (modifier_state == US2400Protocol::MODIFIER_SHIFT) {
+ i->second.shift = action;
+ }
+
+ if (modifier_state == 0) {
+ i->second.plain = action;
+ }
+
+ edited = true;
+
+ save ();
+}
+
+string
+DeviceProfile::name_when_edited (string const& base)
+{
+ return string_compose ("%1 %2", base, edited_indicator);
+}
+
+string
+DeviceProfile::name() const
+{
+ if (edited) {
+ if (_name.find (edited_indicator) == string::npos) {
+ /* modify name to included edited indicator */
+ return name_when_edited (_name);
+ } else {
+ /* name already contains edited indicator */
+ return _name;
+ }
+ } else {
+ return _name;
+ }
+}
+
+void
+DeviceProfile::set_path (const string& p)
+{
+ _path = p;
+}
+
+/* XXX copied from libs/ardour/utils.cc */
+
+static string
+legalize_for_path (const string& str)
+{
+ string::size_type pos;
+ string illegal_chars = "/\\"; /* DOS, POSIX. Yes, we're going to ignore HFS */
+ string legal;
+
+ legal = str;
+ pos = 0;
+
+ while ((pos = legal.find_first_of (illegal_chars, pos)) != string::npos) {
+ legal.replace (pos, 1, "_");
+ pos += 1;
+ }
+
+ return string (legal);
+}
+
+
+void
+DeviceProfile::save ()
+{
+ std::string fullpath = user_devprofile_directory();
+
+ if (g_mkdir_with_parents (fullpath.c_str(), 0755) < 0) {
+ error << string_compose(_("Session: cannot create user MCP profile folder \"%1\" (%2)"), fullpath, strerror (errno)) << endmsg;
+ return;
+ }
+
+ fullpath = Glib::build_filename (fullpath, string_compose ("%1%2", legalize_for_path (name()), devprofile_suffix));
+
+ XMLTree tree;
+ tree.set_root (&get_state());
+
+ if (!tree.write (fullpath)) {
+ error << string_compose ("MCP profile not saved to %1", fullpath) << endmsg;
+ }
+}
diff --git a/libs/surfaces/us2400/device_profile.h b/libs/surfaces/us2400/device_profile.h
new file mode 100644
index 0000000000..eabd5ed33e
--- /dev/null
+++ b/libs/surfaces/us2400/device_profile.h
@@ -0,0 +1,80 @@
+/*
+ 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_us2400_control_protocol_device_profile_h__
+#define __ardour_us2400_control_protocol_device_profile_h__
+
+#include <iostream>
+#include <stdint.h>
+#include <string>
+#include <map>
+
+#include "button.h"
+
+class XMLNode;
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class DeviceProfile
+{
+ public:
+ DeviceProfile (const std::string& name = "");
+ ~DeviceProfile();
+
+ std::string get_button_action (Button::ID, int modifier_state) const;
+ void set_button_action (Button::ID, int modifier_state, const std::string&);
+
+ std::string name() const;
+ void set_path (const std::string&);
+
+ static void reload_device_profiles ();
+ static std::map<std::string,DeviceProfile> device_profiles;
+ static std::string name_when_edited (std::string const& name);
+ static const std::string default_profile_name;
+
+ private:
+ struct ButtonActions {
+ std::string plain;
+ std::string control;
+ std::string shift;
+ std::string option;
+ std::string cmdalt;
+ std::string shiftcontrol;
+ };
+
+ typedef std::map<Button::ID,ButtonActions> ButtonActionMap;
+
+ std::string _name;
+ std::string _path;
+ ButtonActionMap _button_map;
+ bool edited;
+
+ static const std::string edited_indicator;
+
+ int set_state (const XMLNode&, int version);
+ XMLNode& get_state () const;
+
+ void save ();
+};
+
+}
+}
+
+#endif /* __ardour_us2400_control_protocol_device_profile_h__ */
diff --git a/libs/surfaces/us2400/fader.cc b/libs/surfaces/us2400/fader.cc
new file mode 100644
index 0000000000..5c01852f4e
--- /dev/null
+++ b/libs/surfaces/us2400/fader.cc
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 "pbd/compose.h"
+
+#include "ardour/debug.h"
+
+#include "fader.h"
+#include "surface.h"
+#include "control_group.h"
+#include "us2400_control_protocol.h"
+
+using namespace ArdourSurface;
+using namespace US2400;
+using namespace PBD;
+
+Control*
+Fader::factory (Surface& surface, int id, const char* name, Group& group)
+{
+ Fader* f = new Fader (id, name, group);
+
+ surface.faders[id] = f;
+ surface.controls.push_back (f);
+ group.add (*f);
+ return f;
+}
+
+MidiByteArray
+Fader::set_position (float normalized)
+{
+ position = normalized;
+ return update_message ();
+}
+
+MidiByteArray
+Fader::update_message ()
+{
+ int posi = lrintf (16384.0 * position);
+
+ if (posi == last_update_position) {
+ if (posi == llast_update_position) {
+ return MidiByteArray();
+ }
+ }
+
+ llast_update_position = last_update_position;
+ last_update_position = posi;
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("generate fader message for position %1 (%2)\n", position, posi));
+ return MidiByteArray (3, 0xe0 + id(), posi & 0x7f, posi >> 7);
+}
diff --git a/libs/surfaces/us2400/fader.h b/libs/surfaces/us2400/fader.h
new file mode 100644
index 0000000000..169cf454d4
--- /dev/null
+++ b/libs/surfaces/us2400/fader.h
@@ -0,0 +1,38 @@
+#ifndef __ardour_us2400_control_protocol_fader_h__
+#define __ardour_us2400_control_protocol_fader_h__
+
+#include "controls.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Fader : public Control
+{
+ public:
+
+ Fader (int id, std::string name, Group & group)
+ : Control (id, name, group)
+ , position (0.0)
+ , last_update_position (-1)
+ , llast_update_position (-1)
+ {
+ }
+
+ 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;
+ int last_update_position;
+ int llast_update_position;
+};
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/gui.cc b/libs/surfaces/us2400/gui.cc
new file mode 100644
index 0000000000..0f0893c85f
--- /dev/null
+++ b/libs/surfaces/us2400/gui.cc
@@ -0,0 +1,824 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <gtkmm/comboboxtext.h>
+#include <gtkmm/box.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/notebook.h>
+#include <gtkmm/cellrenderercombo.h>
+#include <gtkmm/scale.h>
+#include <gtkmm/alignment.h>
+
+#include "pbd/error.h"
+#include "pbd/unwind.h"
+#include "pbd/strsplit.h"
+#include "pbd/stacktrace.h"
+
+#include "gtkmm2ext/actions.h"
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/port.h"
+#include "ardour/rc_configuration.h"
+
+#include "us2400_control_protocol.h"
+#include "device_info.h"
+#include "gui.h"
+#include "surface.h"
+#include "surface_port.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace Gtk;
+using namespace ArdourSurface;
+using namespace US2400;
+
+void*
+US2400Protocol::get_gui () const
+{
+ if (!_gui) {
+ const_cast<US2400Protocol*>(this)->build_gui ();
+ }
+ static_cast<Gtk::Notebook*>(_gui)->show_all();
+ return _gui;
+}
+
+void
+US2400Protocol::tear_down_gui ()
+{
+ if (_gui) {
+ Gtk::Widget *w = static_cast<Gtk::Widget*>(_gui)->get_parent();
+ if (w) {
+ w->hide();
+ delete w;
+ }
+ }
+ delete (US2400ProtocolGUI*) _gui;
+ _gui = 0;
+}
+
+void
+US2400Protocol::build_gui ()
+{
+ _gui = (void *) new US2400ProtocolGUI (*this);
+}
+
+US2400ProtocolGUI::US2400ProtocolGUI (US2400Protocol& p)
+ : _cp (p)
+ , table (2, 9)
+ , _device_dependent_widget (0)
+ , _ignore_profile_changed (false)
+ , ignore_active_change (false)
+{
+ Gtk::Label* l;
+ Gtk::Alignment* align;
+ int row = 0;
+
+ set_border_width (12);
+
+ table.set_row_spacings (4);
+ table.set_col_spacings (6);
+ table.set_border_width (12);
+ table.set_homogeneous (false);
+
+ _cp.DeviceChanged.connect (device_change_connection, invalidator (*this), boost::bind (&US2400ProtocolGUI::device_changed, this), gui_context());
+ _cp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&US2400ProtocolGUI::connection_handler, this), gui_context());
+
+ /* device-dependent part */
+
+ device_dependent_row = row;
+
+ if (_device_dependent_widget) {
+ table.remove (*_device_dependent_widget);
+ _device_dependent_widget = 0;
+ }
+
+ _device_dependent_widget = device_dependent_widget ();
+ table.attach (*_device_dependent_widget, 0, 12, row, row+1, AttachOptions(0), AttachOptions(0), 0, 0);
+ row++;
+
+ /* back to the boilerplate */
+
+ vector<string> profiles;
+
+ for (std::map<std::string,DeviceProfile>::iterator i = DeviceProfile::device_profiles.begin(); i != DeviceProfile::device_profiles.end(); ++i) {
+ cerr << "add discovered profile " << i->first << endl;
+ profiles.push_back (i->first);
+ }
+ Gtkmm2ext::set_popdown_strings (_profile_combo, profiles);
+ cerr << "set active profile from " << p.device_profile().name() << endl;
+ _profile_combo.set_active_text (p.device_profile().name());
+ _profile_combo.signal_changed().connect (sigc::mem_fun (*this, &US2400ProtocolGUI::profile_combo_changed));
+
+ append_page (table, _("Device Setup"));
+ table.show_all();
+
+ /* function key editor */
+
+ VBox* fkey_packer = manage (new VBox);
+ HBox* profile_packer = manage (new HBox);
+ HBox* observation_packer = manage (new HBox);
+
+ l = manage (new Gtk::Label (_("Profile/Settings:")));
+ profile_packer->pack_start (*l, false, false);
+ profile_packer->pack_start (_profile_combo, true, true);
+ profile_packer->set_spacing (12);
+ profile_packer->set_border_width (12);
+
+ fkey_packer->pack_start (*profile_packer, false, false);
+ fkey_packer->pack_start (function_key_scroller, true, true);
+ fkey_packer->pack_start (*observation_packer, false, false);
+ fkey_packer->set_spacing (12);
+ function_key_scroller.property_shadow_type() = Gtk::SHADOW_NONE;
+ function_key_scroller.add (function_key_editor);
+ append_page (*fkey_packer, _("Function Keys"));
+
+ build_available_action_menu ();
+ build_function_key_editor ();
+ refresh_function_key_editor ();
+ fkey_packer->show_all();
+}
+
+void
+US2400ProtocolGUI::connection_handler ()
+{
+ /* ignore all changes to combobox active strings here, because we're
+ updating them to match a new ("external") reality - we were called
+ because port connections have changed.
+ */
+
+ PBD::Unwinder<bool> ici (ignore_active_change, true);
+
+ vector<Gtk::ComboBox*>::iterator ic;
+ vector<Gtk::ComboBox*>::iterator oc;
+
+ vector<string> midi_inputs;
+ vector<string> midi_outputs;
+
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+ for (ic = input_combos.begin(), oc = output_combos.begin(); ic != input_combos.end() && oc != output_combos.end(); ++ic, ++oc) {
+
+ boost::shared_ptr<Surface> surface = _cp.get_surface_by_raw_pointer ((*ic)->get_data ("surface"));
+
+ if (surface) {
+ update_port_combos (midi_inputs, midi_outputs, *ic, *oc, surface);
+ }
+ }
+}
+
+void
+US2400ProtocolGUI::update_port_combos (vector<string> const& midi_inputs, vector<string> const& midi_outputs,
+ Gtk::ComboBox* input_combo,
+ Gtk::ComboBox* output_combo,
+ boost::shared_ptr<Surface> surface)
+{
+ Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+ Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+ bool input_found = false;
+ bool output_found = false;
+ int n;
+
+ input_combo->set_model (input);
+ output_combo->set_model (output);
+
+ Gtk::TreeModel::Children children = input->children();
+ Gtk::TreeModel::Children::iterator i;
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (surface->port().input().connected_to (port_name)) {
+ input_combo->set_active (n);
+ input_found = true;
+ break;
+ }
+ }
+
+ if (!input_found) {
+ input_combo->set_active (0); /* disconnected */
+ }
+
+ children = output->children();
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (surface->port().output().connected_to (port_name)) {
+ output_combo->set_active (n);
+ output_found = true;
+ break;
+ }
+ }
+
+ if (!output_found) {
+ output_combo->set_active (0); /* disconnected */
+ }
+}
+
+Gtk::Widget*
+US2400ProtocolGUI::device_dependent_widget ()
+{
+ Gtk::Table* dd_table;
+ Gtk::Label* l;
+ int row = 0;
+
+ uint32_t n_surfaces = 1 + _cp.device_info().extenders();
+ uint32_t main_pos = _cp.device_info().master_position();
+
+ dd_table = Gtk::manage (new Gtk::Table (2, n_surfaces));
+ dd_table->set_row_spacings (4);
+ dd_table->set_col_spacings (6);
+ dd_table->set_border_width (12);
+
+ vector<string> midi_inputs;
+ vector<string> midi_outputs;
+
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsPhysical), midi_inputs);
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsPhysical), midi_outputs);
+
+ input_combos.clear ();
+ output_combos.clear ();
+
+ int portcount = n_surfaces;
+
+ for (uint32_t n = 0; n < portcount; ++n) {
+
+ boost::shared_ptr<Surface> surface = _cp.nth_surface (n);
+
+ if (!surface) {
+ PBD::fatal << string_compose (_("programming error: %1\n"), string_compose ("n=%1 surface not found!", n)) << endmsg;
+ /*NOTREACHED*/
+ }
+
+ Gtk::ComboBox* input_combo = manage (new Gtk::ComboBox);
+ Gtk::ComboBox* output_combo = manage (new Gtk::ComboBox);
+
+ update_port_combos (midi_inputs, midi_outputs, input_combo, output_combo, surface);
+
+ input_combo->pack_start (midi_port_columns.short_name);
+ input_combo->set_data ("surface", surface.get());
+ input_combos.push_back (input_combo);
+ output_combo->pack_start (midi_port_columns.short_name);
+ output_combo->set_data ("surface", surface.get());
+ output_combos.push_back (output_combo);
+
+ boost::weak_ptr<Surface> ws (surface);
+ input_combo->signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &US2400ProtocolGUI::active_port_changed), input_combo, ws, true));
+ output_combo->signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &US2400ProtocolGUI::active_port_changed), output_combo, ws, false));
+
+ string send_string;
+ string receive_string;
+
+ //port 1,2,3 are faders & pan knobs. like mackie MCU
+ //port 4 is the joystick
+ //port 5 sends "chan" knobs (24 of them )
+ //port 6 --- ??? ( could be send to knobs... ? )
+
+ send_string = string_compose(_("US-2400 send port #%1 (faders %2 to %3):"), n + 1, n*8+1, n*8+8);
+ receive_string = string_compose(_("US-2400 receive port #%1 (faders %2 to %3):"), n + 1, n*8+1, n*8+8);
+ if (n==3) {
+ send_string = string_compose(_("US-2400 send port #%1 (joystick):"), n + 1);
+ receive_string = string_compose(_("US-2400 receive port #%1 (joystick):"), n + 1);
+ }
+
+ l = manage (new Gtk::Label (send_string));
+ l->set_alignment (1.0, 0.5);
+ dd_table->attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ dd_table->attach (*input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+
+ l = manage (new Gtk::Label (receive_string));
+ l->set_alignment (1.0, 0.5);
+ dd_table->attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ dd_table->attach (*output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+ }
+
+ row++;
+
+ l = manage (new Gtk::Label ("US-2400 Port #5 is reserved for use as a generic USB device. (click the CHAN button to activate)"));
+ l->set_alignment (1.0, 0.5);
+ dd_table->attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ row++;
+ row++;
+
+ l = manage (new Gtk::Label ("US-2400 Port #6 is unused."));
+ l->set_alignment (1.0, 0.5);
+ dd_table->attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ row++;
+ row++;
+
+ l = manage (new Gtk::Label ("NOTE: you must select mode 4 on the US-2400 unit."));
+ l->set_alignment (1.0, 0.5);
+ dd_table->attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ row++;
+
+
+ return dd_table;
+}
+
+CellRendererCombo*
+US2400ProtocolGUI::make_action_renderer (Glib::RefPtr<TreeStore> model, Gtk::TreeModelColumnBase column)
+{
+ CellRendererCombo* renderer = manage (new CellRendererCombo);
+ renderer->property_model() = model;
+ renderer->property_editable() = true;
+ renderer->property_text_column() = 0;
+ renderer->property_has_entry() = false;
+ renderer->signal_edited().connect (sigc::bind (sigc::mem_fun(*this, &US2400ProtocolGUI::action_changed), column));
+
+ return renderer;
+}
+
+void
+US2400ProtocolGUI::build_available_action_menu ()
+{
+ /* build a model of all available actions (needs to be tree structured
+ * more)
+ */
+
+ available_action_model = TreeStore::create (available_action_columns);
+
+ vector<string> paths;
+ vector<string> labels;
+ vector<string> tooltips;
+ vector<string> keys;
+ vector<Glib::RefPtr<Gtk::Action> > actions;
+
+ typedef std::map<string,TreeIter> NodeMap;
+ NodeMap nodes;
+ NodeMap::iterator r;
+
+ Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
+
+ vector<string>::iterator k;
+ vector<string>::iterator p;
+ vector<string>::iterator t;
+ vector<string>::iterator l;
+
+ available_action_model->clear ();
+
+ /* Because there are button bindings built in that are not
+ in the key binding map, there needs to be a way to undo
+ a profile edit.
+ */
+ TreeIter rowp;
+ TreeModel::Row parent;
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[available_action_columns.name] = _("Remove Binding");
+
+ /* Key aliasing */
+
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[available_action_columns.name] = _("Shift");
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[available_action_columns.name] = _("Control");
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[available_action_columns.name] = _("Option");
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[available_action_columns.name] = _("CmdAlt");
+
+ for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
+
+ TreeModel::Row row;
+ vector<string> parts;
+
+ parts.clear ();
+
+ split (*p, parts, '/');
+
+ if (parts.empty()) {
+ continue;
+ }
+
+ //kinda kludgy way to avoid displaying menu items as mappable
+ if ( parts[1] == _("Main_menu") )
+ continue;
+ if ( parts[1] == _("JACK") )
+ continue;
+ if ( parts[1] == _("redirectmenu") )
+ continue;
+ if ( parts[1] == _("Editor_menus") )
+ continue;
+ if ( parts[1] == _("RegionList") )
+ continue;
+ if ( parts[1] == _("ProcessorMenu") )
+ continue;
+
+ if ((r = nodes.find (parts[1])) == nodes.end()) {
+
+ /* top level is missing */
+
+ TreeIter rowp;
+ TreeModel::Row parent;
+ rowp = available_action_model->append();
+ nodes[parts[1]] = rowp;
+ parent = *(rowp);
+ parent[available_action_columns.name] = parts[1];
+
+ row = *(available_action_model->append (parent.children()));
+
+ } else {
+
+ row = *(available_action_model->append ((*r->second)->children()));
+
+ }
+
+ /* add this action */
+
+ if (l->empty ()) {
+ row[available_action_columns.name] = *t;
+ action_map[*t] = *p;
+ } else {
+ row[available_action_columns.name] = *l;
+ action_map[*l] = *p;
+ }
+
+ row[available_action_columns.path] = (*p);
+ }
+}
+
+void
+US2400ProtocolGUI::build_function_key_editor ()
+{
+ function_key_editor.append_column (_("Key"), function_key_columns.name);
+
+ TreeViewColumn* col;
+ CellRendererCombo* renderer;
+
+ renderer = make_action_renderer (available_action_model, function_key_columns.plain);
+ col = manage (new TreeViewColumn (_("Plain"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.plain);
+ function_key_editor.append_column (*col);
+
+ renderer = make_action_renderer (available_action_model, function_key_columns.shift);
+ col = manage (new TreeViewColumn (_("Shift"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.shift);
+ function_key_editor.append_column (*col);
+
+/*
+ * renderer = make_action_renderer (available_action_model, function_key_columns.control);
+ col = manage (new TreeViewColumn (_("Control"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.control);
+ function_key_editor.append_column (*col);
+
+ renderer = make_action_renderer (available_action_model, function_key_columns.option);
+ col = manage (new TreeViewColumn (_("Option"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.option);
+ function_key_editor.append_column (*col);
+
+ renderer = make_action_renderer (available_action_model, function_key_columns.cmdalt);
+ col = manage (new TreeViewColumn (_("Cmd/Alt"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.cmdalt);
+ function_key_editor.append_column (*col);
+
+ renderer = make_action_renderer (available_action_model, function_key_columns.shiftcontrol);
+ col = manage (new TreeViewColumn (_("Shift+Control"), *renderer));
+ col->add_attribute (renderer->property_text(), function_key_columns.shiftcontrol);
+ function_key_editor.append_column (*col);
+*/
+
+ function_key_model = ListStore::create (function_key_columns);
+ function_key_editor.set_model (function_key_model);
+}
+
+void
+US2400ProtocolGUI::refresh_function_key_editor ()
+{
+ function_key_editor.set_model (Glib::RefPtr<TreeModel>());
+ function_key_model->clear ();
+
+ /* now fill with data */
+
+ TreeModel::Row row;
+ DeviceProfile dp (_cp.device_profile());
+ DeviceInfo di;
+
+ for (int n = 0; n < US2400::Button::FinalGlobalButton; ++n) {
+
+ US2400::Button::ID bid = (US2400::Button::ID) n;
+
+ row = *(function_key_model->append());
+ if (di.global_buttons().find (bid) == di.global_buttons().end()) {
+ row[function_key_columns.name] = US2400::Button::id_to_name (bid);
+ } else {
+ row[function_key_columns.name] = di.get_global_button_name (bid) + "*";
+ }
+ row[function_key_columns.id] = bid;
+
+ Glib::RefPtr<Gtk::Action> act;
+ string action;
+ const string defstring = "\u2022";
+
+ /* We only allow plain bindings for Fn keys. All others are
+ * reserved for hard-coded actions.
+ */
+
+ if (bid >= US2400::Button::F1 && bid <= US2400::Button::F6) {
+
+ action = dp.get_button_action (bid, 0);
+ if (action.empty()) {
+ row[function_key_columns.plain] = defstring;
+ } else {
+ if (action.find ('/') == string::npos) {
+ /* Probably a key alias */
+ row[function_key_columns.plain] = action;
+ } else {
+
+ act = ActionManager::get_action (action.c_str());
+ if (act) {
+ row[function_key_columns.plain] = act->get_label();
+ } else {
+ row[function_key_columns.plain] = defstring;
+ }
+ }
+ }
+ }
+
+ //~ /* We only allow plain bindings for Fn keys. All others are
+ //~ * reserved for hard-coded actions.
+ //~ */
+//~
+ //~ if (bid >= US2400::Button::F1 && bid <= US2400::Button::F8) {
+//~
+ //~ action = dp.get_button_action (bid, US2400Protocol::MODIFIER_SHIFT);
+ //~ if (action.empty()) {
+ //~ row[function_key_columns.shift] = defstring;
+ //~ } else {
+ //~ if (action.find ('/') == string::npos) {
+ //~ /* Probably a key alias */
+ //~ row[function_key_columns.shift] = action;
+ //~ } else {
+ //~ act = ActionManager::get_action (action.c_str());
+ //~ if (act) {
+ //~ row[function_key_columns.shift] = act->get_label();
+ //~ } else {
+ //~ row[function_key_columns.shift] = defstring;
+ //~ }
+ //~ }
+ //~ }
+ //~ }
+
+ //~ action = dp.get_button_action (bid, US2400Protocol::MODIFIER_CONTROL);
+ //~ if (action.empty()) {
+ //~ row[function_key_columns.control] = defstring;
+ //~ } else {
+ //~ if (action.find ('/') == string::npos) {
+ //~ /* Probably a key alias */
+ //~ row[function_key_columns.control] = action;
+ //~ } else {
+ //~ act = ActionManager::get_action (action.c_str());
+ //~ if (act) {
+ //~ row[function_key_columns.control] = act->get_label();
+ //~ } else {
+ //~ row[function_key_columns.control] = defstring;
+ //~ }
+ //~ }
+ //~ }
+//~
+ //~ action = dp.get_button_action (bid, US2400Protocol::MODIFIER_OPTION);
+ //~ if (action.empty()) {
+ //~ row[function_key_columns.option] = defstring;
+ //~ } else {
+ //~ if (action.find ('/') == string::npos) {
+ //~ /* Probably a key alias */
+ //~ row[function_key_columns.option] = action;
+ //~ } else {
+ //~ act = ActionManager::get_action (action.c_str());
+ //~ if (act) {
+ //~ row[function_key_columns.option] = act->get_label();
+ //~ } else {
+ //~ row[function_key_columns.option] = defstring;
+ //~ }
+ //~ }
+ //~ }
+//~
+ //~ action = dp.get_button_action (bid, US2400Protocol::MODIFIER_CMDALT);
+ //~ if (action.empty()) {
+ //~ row[function_key_columns.cmdalt] = defstring;
+ //~ } else {
+ //~ if (action.find ('/') == string::npos) {
+ //~ /* Probably a key alias */
+ //~ row[function_key_columns.cmdalt] = action;
+ //~ } else {
+ //~ act = ActionManager::get_action (action.c_str());
+ //~ if (act) {
+ //~ row[function_key_columns.cmdalt] = act->get_label();
+ //~ } else {
+ //~ row[function_key_columns.cmdalt] = defstring;
+ //~ }
+ //~ }
+ //~ }
+//~
+ //~ action = dp.get_button_action (bid, (US2400Protocol::MODIFIER_SHIFT|US2400Protocol::MODIFIER_CONTROL));
+ //~ if (action.empty()) {
+ //~ row[function_key_columns.shiftcontrol] = defstring;
+ //~ } else {
+ //~ act = ActionManager::get_action (action.c_str());
+ //~ if (act) {
+ //~ row[function_key_columns.shiftcontrol] = act->get_label();
+ //~ } else {
+ //~ row[function_key_columns.shiftcontrol] = defstring;
+ //~ }
+ //~ }
+ }
+
+ function_key_editor.set_model (function_key_model);
+}
+
+void
+US2400ProtocolGUI::action_changed (const Glib::ustring &sPath, const Glib::ustring &text, TreeModelColumnBase col)
+{
+ // Remove Binding is not in the action map but still valid
+ bool remove (false);
+ if ( text == "Remove Binding") {
+ remove = true;
+ }
+ Gtk::TreePath path(sPath);
+ Gtk::TreeModel::iterator row = function_key_model->get_iter(path);
+
+ if (row) {
+
+ std::map<std::string,std::string>::iterator i = action_map.find (text);
+
+ if (i == action_map.end()) {
+ if (!remove) {
+ return;
+ }
+ }
+ Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (i->second.c_str());
+
+ if (act || remove) {
+ /* update visible text, using string supplied by
+ available action model so that it matches and is found
+ within the model.
+ */
+ if (remove) {
+ Glib::ustring dot = "\u2022";
+ (*row).set_value (col.index(), dot);
+ } else {
+ (*row).set_value (col.index(), text);
+ }
+
+ /* update the current DeviceProfile, using the full
+ * path
+ */
+
+ int modifier;
+
+ switch (col.index()) {
+ case 3:
+ modifier = US2400Protocol::MODIFIER_SHIFT;
+ break;
+ case 4:
+ modifier = US2400Protocol::MODIFIER_CONTROL;
+ break;
+ case 5:
+ modifier = US2400Protocol::MODIFIER_OPTION;
+ break;
+ case 6:
+ modifier = US2400Protocol::MODIFIER_CMDALT;
+ break;
+ case 7:
+ modifier = (US2400Protocol::MODIFIER_SHIFT|US2400Protocol::MODIFIER_CONTROL);
+ break;
+ default:
+ modifier = 0;
+ }
+
+ if (remove) {
+ _cp.device_profile().set_button_action ((*row)[function_key_columns.id], modifier, "");
+ } else {
+ _cp.device_profile().set_button_action ((*row)[function_key_columns.id], modifier, i->second);
+ }
+
+ _ignore_profile_changed = true;
+ _profile_combo.set_active_text ( _cp.device_profile().name() );
+ _ignore_profile_changed = false;
+
+ } else {
+ std::cerr << "no such action\n";
+ }
+ }
+}
+
+void
+US2400ProtocolGUI::device_changed ()
+{
+ if (_device_dependent_widget) {
+ table.remove (*_device_dependent_widget);
+ _device_dependent_widget = 0;
+ }
+
+ _device_dependent_widget = device_dependent_widget ();
+ _device_dependent_widget->show_all ();
+
+ table.attach (*_device_dependent_widget, 0, 12, device_dependent_row, device_dependent_row+1, AttachOptions(0), AttachOptions(0), 0, 0);
+}
+
+void
+US2400ProtocolGUI::profile_combo_changed ()
+{
+ if (!_ignore_profile_changed) {
+ string profile = _profile_combo.get_active_text();
+
+ _cp.set_profile (profile);
+
+ refresh_function_key_editor ();
+ }
+}
+
+Glib::RefPtr<Gtk::ListStore>
+US2400ProtocolGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+ Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+ TreeModel::Row row;
+
+ row = *store->append ();
+ row[midi_port_columns.full_name] = string();
+ row[midi_port_columns.short_name] = _("Disconnected");
+
+ for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+ row = *store->append ();
+ row[midi_port_columns.full_name] = *p;
+ std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+ if (pn.empty ()) {
+ pn = (*p).substr ((*p).find (':') + 1);
+ }
+ row[midi_port_columns.short_name] = pn;
+ }
+
+ return store;
+}
+
+void
+US2400ProtocolGUI::active_port_changed (Gtk::ComboBox* combo, boost::weak_ptr<Surface> ws, bool for_input)
+{
+ if (ignore_active_change) {
+ return;
+ }
+
+ boost::shared_ptr<Surface> surface = ws.lock();
+
+ if (!surface) {
+ return;
+ }
+
+ TreeModel::iterator active = combo->get_active ();
+ string new_port = (*active)[midi_port_columns.full_name];
+
+ if (new_port.empty()) {
+ if (for_input) {
+ surface->port().input().disconnect_all ();
+ } else {
+ surface->port().output().disconnect_all ();
+ }
+
+ return;
+ }
+
+ if (for_input) {
+ if (!surface->port().input().connected_to (new_port)) {
+ surface->port().input().disconnect_all ();
+ surface->port().input().connect (new_port);
+ }
+ } else {
+ if (!surface->port().output().connected_to (new_port)) {
+ surface->port().output().disconnect_all ();
+ surface->port().output().connect (new_port);
+ }
+ }
+}
diff --git a/libs/surfaces/us2400/gui.h b/libs/surfaces/us2400/gui.h
new file mode 100644
index 0000000000..7e0342bb85
--- /dev/null
+++ b/libs/surfaces/us2400/gui.h
@@ -0,0 +1,142 @@
+/*
+ Copyright (C) 2010-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 <vector>
+
+#include <gtkmm/combobox.h>
+#include <gtkmm/box.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/notebook.h>
+#include <gtkmm/scrolledwindow.h>
+
+namespace Gtk {
+ class CellRendererCombo;
+}
+
+#include "button.h"
+
+#include "pbd/i18n.h"
+
+namespace ArdourSurface {
+
+class US2400Protocol;
+
+namespace US2400 {
+ class Surface;
+}
+
+class US2400ProtocolGUI : public Gtk::Notebook
+{
+ public:
+ US2400ProtocolGUI (US2400Protocol &);
+
+ private:
+ US2400Protocol& _cp;
+ Gtk::Table table;
+ Gtk::ComboBoxText _profile_combo;
+
+ typedef std::vector<Gtk::ComboBox*> PortCombos;
+ PortCombos input_combos;
+ PortCombos output_combos;
+
+ struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+ MidiPortColumns() {
+ add (short_name);
+ add (full_name);
+ }
+ Gtk::TreeModelColumn<std::string> short_name;
+ Gtk::TreeModelColumn<std::string> full_name;
+ };
+
+ struct AvailableActionColumns : public Gtk::TreeModel::ColumnRecord {
+ AvailableActionColumns() {
+ add (name);
+ add (path);
+ }
+ Gtk::TreeModelColumn<std::string> name;
+ Gtk::TreeModelColumn<std::string> path;
+ };
+
+ struct FunctionKeyColumns : public Gtk::TreeModel::ColumnRecord {
+ FunctionKeyColumns() {
+ add (name);
+ add (id);
+ add (plain);
+ add (shift);
+ add (control);
+ add (option);
+ add (cmdalt);
+ add (shiftcontrol);
+ };
+ Gtk::TreeModelColumn<std::string> name;
+ Gtk::TreeModelColumn<US2400::Button::ID> id;
+ Gtk::TreeModelColumn<std::string> plain;
+ Gtk::TreeModelColumn<std::string> shift;
+ Gtk::TreeModelColumn<std::string> control;
+ Gtk::TreeModelColumn<std::string> option;
+ Gtk::TreeModelColumn<std::string> cmdalt;
+ Gtk::TreeModelColumn<std::string> shiftcontrol;
+ };
+
+ AvailableActionColumns available_action_columns;
+ FunctionKeyColumns function_key_columns;
+ MidiPortColumns midi_port_columns;
+
+ Gtk::ScrolledWindow function_key_scroller;
+ Gtk::TreeView function_key_editor;
+ Glib::RefPtr<Gtk::ListStore> function_key_model;
+ Glib::RefPtr<Gtk::TreeStore> available_action_model;
+
+ Glib::RefPtr<Gtk::ListStore> build_midi_port_list (bool for_input);
+
+ void build_available_action_menu ();
+ void refresh_function_key_editor ();
+ void build_function_key_editor ();
+ void action_changed (const Glib::ustring &sPath, const Glib::ustring &text, Gtk::TreeModelColumnBase);
+ Gtk::CellRendererCombo* make_action_renderer (Glib::RefPtr<Gtk::TreeStore> model, Gtk::TreeModelColumnBase);
+
+ void profile_combo_changed ();
+
+ std::map<std::string,std::string> action_map; // map from action names to paths
+
+ Gtk::Widget* device_dependent_widget ();
+ Gtk::Widget* _device_dependent_widget;
+ int device_dependent_row;
+
+ PBD::ScopedConnection device_change_connection;
+ void device_changed ();
+
+ void update_port_combos (std::vector<std::string> const&, std::vector<std::string> const&,
+ Gtk::ComboBox* input_combo,
+ Gtk::ComboBox* output_combo,
+ boost::shared_ptr<US2400::Surface> surface);
+
+ PBD::ScopedConnection connection_change_connection;
+ void connection_handler ();
+
+ Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+ bool _ignore_profile_changed;
+ bool ignore_active_change;
+ void active_port_changed (Gtk::ComboBox* combo, boost::weak_ptr<US2400::Surface> ws, bool for_input);
+};
+
+}
+
diff --git a/libs/surfaces/us2400/interface.cc b/libs/surfaces/us2400/interface.cc
new file mode 100644
index 0000000000..b046544ceb
--- /dev/null
+++ b/libs/surfaces/us2400/interface.cc
@@ -0,0 +1,102 @@
+/*
+ Copyright (C) 2006,2007 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <stdexcept>
+
+#include "pbd/error.h"
+
+#include "ardour/rc_configuration.h"
+
+#include "control_protocol/control_protocol.h"
+#include "us2400_control_protocol.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+using namespace ArdourSurface;
+using namespace US2400;
+
+static ControlProtocol*
+new_us2400_protocol (ControlProtocolDescriptor*, Session* s)
+{
+ US2400Protocol* mcp = 0;
+
+ try {
+ mcp = new US2400Protocol (*s);
+ /* do not set active here - wait for set_state() */
+ }
+ catch (exception & e) {
+ error << "Error instantiating US-2400: " << e.what() << endmsg;
+ delete mcp;
+ mcp = 0;
+ }
+
+ return mcp;
+}
+
+static void
+delete_us2400_protocol (ControlProtocolDescriptor*, ControlProtocol* cp)
+{
+ try
+ {
+ delete cp;
+ }
+ catch ( exception & e )
+ {
+ cout << "Exception caught trying to destroy US-2400: " << e.what() << endl;
+ }
+}
+
+/**
+ This is called on startup to check whether the lib should be loaded.
+
+ So anything that can be changed in the UI should not be used here to
+ prevent loading of the lib.
+*/
+static bool
+probe_us2400_protocol (ControlProtocolDescriptor*)
+{
+ return US2400Protocol::probe();
+}
+
+static void*
+us2400_request_buffer_factory (uint32_t num_requests)
+{
+ return US2400Protocol::request_factory (num_requests);
+}
+
+// Field names commented out by JE - 06-01-2010
+static ControlProtocolDescriptor us2400_descriptor = {
+ /*name : */ "Tascam US-2400",
+ /*id : */ "uri://ardour.org/surfaces/us2400:0",
+ /*ptr : */ 0,
+ /*module : */ 0,
+ /*mandatory : */ 0,
+ // actually, the surface does support feedback, but all this
+ // flag does is show a submenu on the UI, which is useless for the mackie
+ // because feedback is always on. In any case, who'd want to use the
+ // mcu without the motorised sliders doing their thing?
+ /*supports_feedback : */ false,
+ /*probe : */ probe_us2400_protocol,
+ /*initialize : */ new_us2400_protocol,
+ /*destroy : */ delete_us2400_protocol,
+ /*request_buffer_factory */ us2400_request_buffer_factory
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &us2400_descriptor; }
diff --git a/libs/surfaces/us2400/jog.cc b/libs/surfaces/us2400/jog.cc
new file mode 100644
index 0000000000..5021dd5bba
--- /dev/null
+++ b/libs/surfaces/us2400/jog.cc
@@ -0,0 +1,37 @@
+/*
+ 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 "jog.h"
+#include "surface.h"
+#include "control_group.h"
+
+using namespace ArdourSurface;
+using namespace US2400;
+
+const int Jog::ID = 0x3c;
+
+Control*
+Jog::factory (Surface& surface, int id, const char* name, Group& group)
+{
+ Jog* j = new Jog (id, name, group);
+ surface.pots[id] = j;
+ surface.controls.push_back (j);
+ group.add (*j);
+ return j;
+}
diff --git a/libs/surfaces/us2400/jog.h b/libs/surfaces/us2400/jog.h
new file mode 100644
index 0000000000..5162fd9cb2
--- /dev/null
+++ b/libs/surfaces/us2400/jog.h
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+
+ 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_us2400_control_protocol_jog_h__
+#define __ardour_us2400_control_protocol_jog_h__
+
+#include "controls.h"
+#include "pot.h"
+
+namespace ArdourSurface {
+namespace US2400 {
+
+class Jog : public Pot
+{
+public:
+ static const int ID;
+
+ Jog (int id, std::string name, Group & group)
+ : Pot (id, name, group)
+ {
+ }
+
+ MidiByteArray zero() { return MidiByteArray(); }
+
+ static Control* factory (Surface&, int id, const char*, Group&);
+};
+
+}
+}
+
+#endif /* __ardour_us2400_control_protocol_jog_h__ */
diff --git a/libs/surfaces/us2400/jog_wheel.cc b/libs/surfaces/us2400/jog_wheel.cc
new file mode 100644
index 0000000000..d7591e6ffb
--- /dev/null
+++ b/libs/surfaces/us2400/jog_wheel.cc
@@ -0,0 +1,70 @@
+/*
+ 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 "ardour/session.h"
+
+#include "jog_wheel.h"
+#include "us2400_control_protocol.h"
+#include "surface_port.h"
+#include "controls.h"
+#include "surface.h"
+
+#include <algorithm>
+
+using namespace ArdourSurface;
+using namespace US2400;
+
+JogWheel::JogWheel (US2400Protocol & mcp)
+ : _mcp (mcp)
+ , _mode (scroll)
+{
+}
+
+void
+JogWheel::set_mode (Mode m)
+{
+ _mode = m;
+}
+
+void JogWheel::jog_event (float delta)
+{
+ if (_mcp.zoom_mode()) {
+ if (delta > 0) {
+ for (unsigned int i = 0; i < fabs (delta); ++i) {
+ _mcp.ZoomIn();
+ }
+ } else {
+ for (unsigned int i = 0; i < fabs (delta); ++i) {
+ _mcp.ZoomOut();
+ }
+ }
+ return;
+ }
+
+ switch (_mode) {
+ case scroll:
+ _mcp.ScrollTimeline (delta/4.0);
+ break;
+ default:
+ break;
+ }
+}
+
diff --git a/libs/surfaces/us2400/jog_wheel.h b/libs/surfaces/us2400/jog_wheel.h
new file mode 100644
index 0000000000..5c1dbee750
--- /dev/null
+++ b/libs/surfaces/us2400/jog_wheel.h
@@ -0,0 +1,37 @@
+#ifndef mackie_jog_wheel
+#define mackie_jog_wheel
+
+#include "timer.h"
+
+#include <stack>
+#include <deque>
+#include <queue>
+
+namespace ArdourSurface {
+
+class US2400Protocol;
+
+namespace US2400
+{
+
+class JogWheel
+{
+ public:
+ enum Mode { scroll };
+
+ JogWheel (US2400Protocol & mcp);
+
+ /// As the wheel turns...
+ void jog_event (float delta);
+ void set_mode (Mode m);
+ Mode mode() const { return _mode; }
+
+private:
+ US2400Protocol & _mcp;
+ Mode _mode;
+};
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/led.cc b/libs/surfaces/us2400/led.cc
new file mode 100644
index 0000000000..49466ad957
--- /dev/null
+++ b/libs/surfaces/us2400/led.cc
@@ -0,0 +1,75 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 "led.h"
+#include "surface.h"
+#include "control_group.h"
+
+using namespace ArdourSurface;
+using namespace US2400;
+
+const int Led::FaderTouch = 0x70;
+const int Led::Timecode = 0x71;
+const int Led::Beats = 0x72;
+const int Led::RudeSolo = 0x73;
+const int Led::RelayClick = 0x74;
+
+Control*
+Led::factory (Surface& surface, int id, const char* name, Group& group)
+{
+ Led* l = new Led (id, name, group);
+ surface.leds[id] = l;
+ surface.controls.push_back (l);
+ group.add (*l);
+ return l;
+}
+
+MidiByteArray
+Led::set_state (LedState new_state)
+{
+ if (new_state == last_state) {
+ if (new_state == llast_state) {
+ return MidiByteArray ();
+ }
+ }
+ llast_state = last_state;
+ last_state = new_state;
+
+
+ state = new_state;
+
+ MIDI::byte msg = 0;
+
+ switch (state.state()) {
+ case LedState::on:
+ msg = 0x7f;
+ break;
+ case LedState::off:
+ msg = 0x00;
+ break;
+ case LedState::flashing:
+ msg = 0x01;
+ break;
+ case LedState::none:
+ return MidiByteArray ();
+ }
+
+ return MidiByteArray (3, 0x90, id(), msg);
+}
diff --git a/libs/surfaces/us2400/led.h b/libs/surfaces/us2400/led.h
new file mode 100644
index 0000000000..99514b12ac
--- /dev/null
+++ b/libs/surfaces/us2400/led.h
@@ -0,0 +1,66 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+
+ 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_us2400_control_protocol_led_h__
+#define __ardour_us2400_control_protocol_led_h__
+
+#include "controls.h"
+#include "midi_byte_array.h"
+#include "types.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Led : public Control
+{
+public:
+ static const int FaderTouch;
+ static const int Timecode;
+ static const int Beats;
+ static const int RudeSolo;
+ static const int RelayClick;
+
+ Led (int id, std::string name, Group & group)
+ : Control (id, name, group)
+ , state (off)
+ , last_state (off)
+ , llast_state (off)
+ {
+ }
+
+ Led & led() { return *this; }
+ MidiByteArray set_state (LedState);
+
+ MidiByteArray zero() { return set_state (off); }
+
+ static Control* factory (Surface&, int id, const char*, Group&);
+
+ private:
+ LedState state;
+ LedState last_state;
+ LedState llast_state;
+};
+
+}
+}
+
+#endif /* __ardour_us2400_control_protocol_led_h__ */
diff --git a/libs/surfaces/us2400/mcp_buttons.cc b/libs/surfaces/us2400/mcp_buttons.cc
new file mode 100644
index 0000000000..69a13cdd85
--- /dev/null
+++ b/libs/surfaces/us2400/mcp_buttons.cc
@@ -0,0 +1,1111 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <algorithm>
+
+#include "pbd/memento_command.h"
+
+#include "ardour/debug.h"
+#include "ardour/profile.h"
+#include "ardour/session.h"
+#include "ardour/route.h"
+#include "ardour/location.h"
+#include "ardour/rc_configuration.h"
+
+#include "us2400_control_protocol.h"
+#include "surface.h"
+#include "fader.h"
+
+#include "pbd/i18n.h"
+
+/* handlers for all buttons, broken into a separate file to avoid clutter in
+ * us2400_control_protocol.cc
+ */
+
+using std::string;
+using namespace ARDOUR;
+using namespace PBD;
+using namespace ArdourSurface;
+using namespace US2400;
+
+LedState
+US2400Protocol::shift_press (Button &)
+{
+ _modifier_state |= MODIFIER_SHIFT;
+ return on;
+}
+LedState
+US2400Protocol::shift_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_SHIFT;
+ return off;
+}
+LedState
+US2400Protocol::option_press (Button &)
+{
+ _modifier_state |= MODIFIER_OPTION;
+ return on;
+}
+LedState
+US2400Protocol::option_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_OPTION;
+ return off;
+}
+LedState
+US2400Protocol::control_press (Button &)
+{
+ _modifier_state |= MODIFIER_CONTROL;
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("CONTROL Press: modifier state now set to %1\n", _modifier_state));
+ return on;
+}
+LedState
+US2400Protocol::control_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_CONTROL;
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("CONTROL Release: modifier state now set to %1\n", _modifier_state));
+ return off;
+}
+LedState
+US2400Protocol::cmd_alt_press (Button &)
+{
+ _modifier_state |= MODIFIER_CMDALT;
+ return on;
+}
+LedState
+US2400Protocol::cmd_alt_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_CMDALT;
+ return off;
+}
+
+LedState
+US2400Protocol::left_press (Button &)
+{
+ if (_subview_mode != None) {
+ return none;
+ }
+
+ Sorted sorted = get_sorted_stripables();
+ uint32_t strip_cnt = n_strips ();
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("bank left with current initial = %1 nstrips = %2 tracks/busses = %3\n",
+ _current_initial_bank, strip_cnt, sorted.size()));
+ if (_current_initial_bank > 0) {
+ (void) switch_banks ((_current_initial_bank - 1) / strip_cnt * strip_cnt);
+ } else {
+ (void) switch_banks (0);
+ }
+
+
+ return on;
+}
+
+LedState
+US2400Protocol::left_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::right_press (Button &)
+{
+ if (_subview_mode != None) {
+ return none;
+ }
+
+ Sorted sorted = get_sorted_stripables();
+ uint32_t strip_cnt = n_strips();
+ uint32_t route_cnt = sorted.size();
+ uint32_t max_bank = route_cnt / strip_cnt * strip_cnt;
+
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("bank right with current initial = %1 nstrips = %2 tracks/busses = %3\n",
+ _current_initial_bank, strip_cnt, route_cnt));
+
+ if (_current_initial_bank < max_bank) {
+ uint32_t new_initial = (_current_initial_bank / strip_cnt * strip_cnt) + strip_cnt;
+ (void) switch_banks (new_initial);
+ }
+
+ return none;
+}
+
+LedState
+US2400Protocol::right_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::cursor_left_press (Button& )
+{
+ if (zoom_mode()) {
+
+ if (main_modifier_state() & MODIFIER_OPTION) {
+ /* reset selected tracks to default vertical zoom */
+ } else {
+ ZoomOut (); /* EMIT SIGNAL */
+ }
+ } else {
+ float page_fraction;
+ if (main_modifier_state() == MODIFIER_CONTROL) {
+ page_fraction = 1.0;
+ } else if (main_modifier_state() == MODIFIER_OPTION) {
+ page_fraction = 0.1;
+ } else if (main_modifier_state() == MODIFIER_SHIFT) {
+ page_fraction = 2.0;
+ } else {
+ page_fraction = 0.25;
+ }
+
+ ScrollTimeline (-page_fraction);
+ }
+
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_left_release (Button&)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_right_press (Button& )
+{
+ if (zoom_mode()) {
+
+ if (main_modifier_state() & MODIFIER_OPTION) {
+ /* reset selected tracks to default vertical zoom */
+ } else {
+ ZoomIn (); /* EMIT SIGNAL */
+ }
+ } else {
+ float page_fraction;
+ if (main_modifier_state() == MODIFIER_CONTROL) {
+ page_fraction = 1.0;
+ } else if (main_modifier_state() == MODIFIER_OPTION) {
+ page_fraction = 0.1;
+ } else if (main_modifier_state() == MODIFIER_SHIFT) {
+ page_fraction = 2.0;
+ } else {
+ page_fraction = 0.25;
+ }
+
+ ScrollTimeline (page_fraction);
+ }
+
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_right_release (Button&)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_up_press (Button&)
+{
+ if (zoom_mode()) {
+
+ if (main_modifier_state() & MODIFIER_CONTROL) {
+ VerticalZoomInSelected (); /* EMIT SIGNAL */
+ } else {
+ VerticalZoomInAll (); /* EMIT SIGNAL */
+ }
+ } else {
+ access_action ("Editor/select-prev-route");
+ }
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_up_release (Button&)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_down_press (Button&)
+{
+ if (zoom_mode()) {
+ if (main_modifier_state() & MODIFIER_OPTION) {
+ VerticalZoomOutSelected (); /* EMIT SIGNAL */
+ } else {
+ VerticalZoomOutAll (); /* EMIT SIGNAL */
+ }
+ } else {
+ access_action ("Editor/select-next-route");
+ }
+ return off;
+}
+
+LedState
+US2400Protocol::cursor_down_release (Button&)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::channel_left_press (Button &)
+{
+ if (_subview_mode != None) {
+ return none;
+ }
+ Sorted sorted = get_sorted_stripables();
+ if (sorted.size() > n_strips()) {
+ prev_track();
+ return on;
+ } else {
+ return flashing;
+ }
+}
+
+LedState
+US2400Protocol::channel_left_release (Button &)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::channel_right_press (Button &)
+{
+ if (_subview_mode != None) {
+ return none;
+ }
+ Sorted sorted = get_sorted_stripables();
+ if (sorted.size() > n_strips()) {
+ next_track();
+ return on;
+ } else {
+ return flashing;
+ }
+}
+
+LedState
+US2400Protocol::channel_right_release (Button &)
+{
+ return off;
+}
+
+US2400::LedState
+US2400Protocol::zoom_press (US2400::Button &)
+{
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::zoom_release (US2400::Button &)
+{
+ if (_modifier_state & MODIFIER_ZOOM) {
+ _modifier_state &= ~MODIFIER_ZOOM;
+ } else {
+ _modifier_state |= MODIFIER_ZOOM;
+ }
+
+ return (zoom_mode() ? on : off);
+}
+
+US2400::LedState
+US2400Protocol::scrub_press (US2400::Button &)
+{
+ if (!surfaces.empty()) {
+ // surfaces.front()->next_jog_mode ();
+ _master_surface->next_jog_mode ();
+ }
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::scrub_release (US2400::Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::undo_press (Button&)
+{
+ if (main_modifier_state() == MODIFIER_SHIFT) {
+ redo();
+ } else {
+ undo ();
+ }
+ return none;
+}
+
+LedState
+US2400Protocol::undo_release (Button&)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::drop_press (Button &)
+{
+ _modifier_state |= MODIFIER_DROP;
+printf("drop press, modifier drop state = %d\n", _modifier_state);
+
+ return none;
+}
+
+LedState
+US2400Protocol::drop_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_DROP;
+printf("drop release, modifier drop state = %d\n", _modifier_state);
+
+ return none;
+}
+
+LedState
+US2400Protocol::save_press (Button &)
+{
+ if (main_modifier_state() == MODIFIER_SHIFT) {
+ quick_snapshot_switch();
+ } else {
+ save_state ();
+ }
+
+ return none;
+}
+
+LedState
+US2400Protocol::save_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::timecode_beats_press (Button &)
+{
+ switch (_timecode_type) {
+ case ARDOUR::AnyTime::BBT:
+ _timecode_type = ARDOUR::AnyTime::Timecode;
+ break;
+ case ARDOUR::AnyTime::Timecode:
+ _timecode_type = ARDOUR::AnyTime::BBT;
+ break;
+ default:
+ return off;
+ }
+
+ update_timecode_beats_led();
+
+ return on;
+}
+
+LedState
+US2400Protocol::timecode_beats_release (Button &)
+{
+ return off;
+}
+
+/////////////////////////////////////
+// Functions
+/////////////////////////////////////
+LedState
+US2400Protocol::marker_press (Button &)
+{
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Common/remove-location-from-playhead");
+ return off;
+ } else {
+ _modifier_state |= MODIFIER_MARKER;
+ marker_modifier_consumed_by_button = false;
+ return on;
+ }
+}
+
+LedState
+US2400Protocol::marker_release (Button &)
+{
+ _modifier_state &= ~MODIFIER_MARKER;
+
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ return off; //if shift was held, we already did the action
+ }
+
+ if (marker_modifier_consumed_by_button) {
+ DEBUG_TRACE (DEBUG::US2400, "marked modifier consumed by button, ignored\n");
+ /* marker was used a modifier for some other button(s), so do
+ nothing
+ */
+ return off;
+ }
+
+ string markername;
+
+ /* Don't add another mark if one exists within 1/100th of a second of
+ * the current position and we're not rolling.
+ */
+
+ samplepos_t where = session->audible_sample();
+
+ if (session->transport_stopped() && session->locations()->mark_at (where, session->sample_rate() / 100.0)) {
+ return off;
+ }
+
+ session->locations()->next_available_name (markername,"mark");
+ add_marker (markername);
+
+ return off;
+}
+
+/////////////////////////////////////
+// Transport Buttons
+/////////////////////////////////////
+
+LedState
+US2400Protocol::stop_press (Button &)
+{
+ transport_stop ();
+
+ if (main_modifier_state() == MODIFIER_SHIFT) {
+ session->midi_panic();
+ }
+
+ return on;
+}
+
+LedState
+US2400Protocol::stop_release (Button &)
+{
+ return session->transport_stopped();
+}
+
+LedState
+US2400Protocol::play_press (Button &)
+{
+ /* if we're already rolling at normal speed, and we're pressed
+ again, jump back to where we started last time
+ */
+
+ transport_play (session->transport_speed() == 1.0);
+ return none;
+}
+
+LedState
+US2400Protocol::play_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::record_press (Button &)
+{
+ rec_enable_toggle ();
+ return none;
+}
+
+LedState
+US2400Protocol::record_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::rewind_press (Button &)
+{
+ if (modifier_state() & MODIFIER_MARKER) {
+ prev_marker ();
+ } else if ( (_modifier_state & MODIFIER_DROP) == MODIFIER_DROP) {
+ access_action ("Common/start-range-from-playhead");
+ } else if (main_modifier_state() & MODIFIER_SHIFT) {
+ goto_start ();
+ } else {
+ rewind ();
+ }
+ return none;
+}
+
+LedState
+US2400Protocol::rewind_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::ffwd_press (Button &)
+{
+ if (modifier_state() & MODIFIER_MARKER) {
+ next_marker ();
+ } else if ( (_modifier_state & MODIFIER_DROP) == MODIFIER_DROP) {
+ access_action ("Common/finish-range-from-playhead");
+ } else if (main_modifier_state() & MODIFIER_SHIFT) {
+ goto_end();
+ } else {
+ ffwd ();
+ }
+ return none;
+}
+
+LedState
+US2400Protocol::ffwd_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::loop_press (Button &)
+{
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Common/set-loop-from-edit-range");
+ return off;
+ } else {
+ bool was_on = session->get_play_loop();
+ loop_toggle ();
+ return was_on ? off : on;
+ }
+}
+
+LedState
+US2400Protocol::loop_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::enter_press (Button &)
+{
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Transport/ToggleFollowEdits");
+ } else {
+ access_action ("Editor/select-all-tracks");
+ }
+ return none;
+}
+
+LedState
+US2400Protocol::enter_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::bank_release (Button& b, uint32_t basic_bank_num)
+{
+ if (_subview_mode != None) {
+ return none;
+ }
+
+ uint32_t bank_num = basic_bank_num;
+
+ if (b.long_press_count() > 0) {
+ bank_num = 8 + basic_bank_num;
+ }
+
+ (void) switch_banks (n_strips() * bank_num);
+
+ return on;
+}
+
+/* F-KEYS are only used for actions that are bound from the control panel; no need to address them here
+LedState
+US2400Protocol::F1_press (Button &b)
+{
+ return on;
+}
+LedState
+US2400Protocol::F1_release (Button &b)
+{
+ return off;
+}
+LedState
+US2400Protocol::F2_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F2_release (Button &b)
+{
+ return off;
+}
+LedState
+US2400Protocol::F3_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F3_release (Button &b)
+{
+ return off;
+}
+LedState
+US2400Protocol::F4_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F4_release (Button &b)
+{
+ return off;
+}
+LedState
+US2400Protocol::F5_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F5_release (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::F6_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F6_release (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::F7_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F7_release (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::F8_press (Button &)
+{
+ return on;
+}
+LedState
+US2400Protocol::F8_release (Button &)
+{
+ return off;
+}
+*/
+
+
+/* UNIMPLEMENTED */
+
+LedState
+US2400Protocol::pan_press (Button &)
+{
+ //US-2400: deselect all strips when the user asks for "Pan". This resets us to default showing the panner only
+ access_action ("Mixer/select-none");
+
+ return none;
+}
+LedState
+US2400Protocol::pan_release (Button &)
+{
+ return none;
+}
+LedState
+US2400Protocol::plugin_press (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::plugin_release (Button &)
+{
+ // Do not do this yet, since it does nothing
+ // set_view_mode (Plugins);
+ return none; /* LED state set by set_view_mode */
+}
+LedState
+US2400Protocol::eq_press (Button &)
+{
+ return none; /* led state handled by set_subview_mode() */
+
+}
+LedState
+US2400Protocol::eq_release (Button &)
+{
+ return none;
+}
+LedState
+US2400Protocol::dyn_press (Button &)
+{
+ return none; /* led state handled by set_subview_mode() */
+}
+
+LedState
+US2400Protocol::dyn_release (Button &)
+{
+ return none;
+}
+
+LedState
+US2400Protocol::flip_press (Button &)
+{
+ if (_view_mode == Busses) {
+ set_view_mode (Mixer);
+ return off;
+ } else {
+ set_view_mode (Busses);
+ return on;
+ }
+}
+
+LedState
+US2400Protocol::flip_release (Button &)
+{
+ return none;
+}
+LedState
+US2400Protocol::name_value_press (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::name_value_release (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::touch_press (Button &)
+{
+ return none;
+}
+LedState
+US2400Protocol::touch_release (Button &)
+{
+ set_automation_state (ARDOUR::Touch);
+ return none;
+}
+LedState
+US2400Protocol::cancel_press (Button &)
+{
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Transport/ToggleExternalSync");
+ } else {
+ access_action ("Main/Escape");
+ }
+ return none;
+}
+LedState
+US2400Protocol::cancel_release (Button &)
+{
+ return none;
+}
+LedState
+US2400Protocol::user_a_press (Button &)
+{
+ transport_play (session->transport_speed() == 1.0);
+ return off;
+}
+LedState
+US2400Protocol::user_a_release (Button &)
+{
+ return off;
+}
+LedState
+US2400Protocol::user_b_press (Button &)
+{
+ transport_stop();
+ return off;
+}
+LedState
+US2400Protocol::user_b_release (Button &)
+{
+ return off;
+}
+
+LedState
+US2400Protocol::master_fader_touch_press (US2400::Button &)
+{
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::master_fader_touch_press\n");
+
+ Fader* master_fader = _master_surface->master_fader();
+
+ boost::shared_ptr<AutomationControl> ac = master_fader->control ();
+
+ master_fader->set_in_use (true);
+ master_fader->start_touch (transport_frame());
+
+ return none;
+}
+LedState
+US2400Protocol::master_fader_touch_release (US2400::Button &)
+{
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::master_fader_touch_release\n");
+
+ Fader* master_fader = _master_surface->master_fader();
+
+ master_fader->set_in_use (false);
+ master_fader->stop_touch (transport_frame());
+
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::read_press (US2400::Button&)
+{
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::read_release (US2400::Button&)
+{
+ set_automation_state (ARDOUR::Play);
+ return none;
+}
+US2400::LedState
+US2400Protocol::write_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::write_release (US2400::Button&)
+{
+ set_automation_state (ARDOUR::Write);
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::clearsolo_press (US2400::Button&)
+{
+ // clears all solos and listens (pfl/afl)
+ if (main_modifier_state() & MODIFIER_OPTION) {
+ cancel_all_solo ();
+ }
+
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::clearsolo_release (US2400::Button&)
+{
+ //return session->soloing();
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::track_press (US2400::Button&)
+{
+ set_subview_mode (TrackView, first_selected_stripable());
+ return none;
+}
+US2400::LedState
+US2400Protocol::track_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::send_press (US2400::Button&)
+{
+// _modifier_state |= MODIFIER_AUX; //US2400 ... AUX button is some kind of modifier
+// return on;
+
+// set_subview_mode (Sends, first_selected_stripable());
+
+ //DO NOTHING
+
+ return none; /* led state handled by set_subview_mode() */
+}
+US2400::LedState
+US2400Protocol::send_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::miditracks_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::miditracks_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::inputs_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::inputs_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::audiotracks_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::audiotracks_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::audioinstruments_press (US2400::Button& b)
+{
+ return none;
+}
+
+US2400::LedState
+US2400Protocol::audioinstruments_release (US2400::Button& b)
+{
+ return none;
+
+}
+US2400::LedState
+US2400Protocol::aux_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::aux_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::busses_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::busses_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::outputs_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::outputs_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::user_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::user_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::trim_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::trim_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::latch_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::latch_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::grp_press (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::grp_release (US2400::Button&)
+{
+ /* There is no "Off" button for automation,
+ so we use Group for this purpose.
+ */
+ set_automation_state (Off);
+ return none;
+}
+US2400::LedState
+US2400Protocol::nudge_press (US2400::Button&)
+{
+// _modifier_state |= MODIFIER_NUDGE; //no such button on US2400
+ nudge_modifier_consumed_by_button = false;
+ return on;
+}
+US2400::LedState
+US2400Protocol::nudge_release (US2400::Button&)
+{
+// _modifier_state &= ~MODIFIER_NUDGE; //no such button on US2400
+
+ /* XXX these action names are stupid, because the action can affect
+ * regions, markers or the playhead depending on selection state.
+ */
+
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Region/nudge-backward");
+ } else {
+ access_action ("Region/nudge-forward");
+ }
+
+ return off;
+}
+US2400::LedState
+US2400Protocol::replace_press (US2400::Button&)
+{
+ if (main_modifier_state() == MODIFIER_SHIFT) {
+ toggle_punch_out();
+ return none;
+ } else {
+ access_action ("Common/finish-range-from-playhead");
+ }
+ return none;
+}
+US2400::LedState
+US2400Protocol::replace_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::click_press (US2400::Button&)
+{
+ if (main_modifier_state() & MODIFIER_SHIFT) {
+ access_action ("Common/set-punch-from-edit-range");
+ return off;
+ } else {
+ bool state = !Config->get_clicking();
+ Config->set_clicking (state);
+ return state;
+ }
+}
+US2400::LedState
+US2400Protocol::click_release (US2400::Button&)
+{
+ return none;
+}
+US2400::LedState
+US2400Protocol::view_press (US2400::Button&)
+{
+ set_view_mode (Mixer);
+ return none;
+}
+US2400::LedState
+US2400Protocol::view_release (US2400::Button&)
+{
+ return none;
+}
diff --git a/libs/surfaces/us2400/meter.cc b/libs/surfaces/us2400/meter.cc
new file mode 100644
index 0000000000..dd08724ff5
--- /dev/null
+++ b/libs/surfaces/us2400/meter.cc
@@ -0,0 +1,137 @@
+/*
+ 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 "pbd/compose.h"
+#include "ardour/debug.h"
+
+#include "meter.h"
+#include "surface.h"
+#include "surface_port.h"
+#include "control_group.h"
+#include "us2400_control_protocol.h"
+
+using namespace PBD;
+using namespace ArdourSurface;
+using namespace US2400;
+
+Control*
+Meter::factory (Surface& surface, int id, const char* name, Group& group)
+{
+ Meter* m = new Meter (id, name, group);
+ surface.meters[id] = m;
+ surface.controls.push_back (m);
+ group.add (*m);
+ return m;
+}
+
+void
+Meter::notify_metering_state_changed(Surface& surface, bool transport_is_rolling, bool metering_active)
+{
+return;
+
+ MidiByteArray msg;
+
+ // sysex header
+ msg << surface.sysex_hdr();
+
+ // code for Channel Meter Enable Message
+ msg << 0x20;
+
+ // Channel identification
+ msg << id();
+
+ // Enable (0x07) / Disable (0x00) level meter on LCD, peak hold display on horizontal meter and signal LED
+ _enabled = ((surface.mcp().device_info().has_separate_meters() || transport_is_rolling) && metering_active);
+ msg << (_enabled ? 0x07 : 0x00);
+
+ // sysex trailer
+ msg << MIDI::eox;
+
+ surface.write (msg);
+}
+
+void
+Meter::send_update (Surface& surface, float dB)
+{
+
+ float def = 0.0f; /* Meter deflection %age */
+
+ // DEBUG_TRACE (DEBUG::US2400, string_compose ("Meter ID %1 dB %2\n", id(), dB));
+
+ if (dB < -70.0f) {
+ def = 0.0f;
+ } else if (dB < -60.0f) {
+ def = (dB + 70.0f) * 0.25f;
+ } else if (dB < -50.0f) {
+ def = (dB + 60.0f) * 0.5f + 2.5f;
+ } else if (dB < -40.0f) {
+ def = (dB + 50.0f) * 0.75f + 7.5f;
+ } else if (dB < -30.0f) {
+ def = (dB + 40.0f) * 1.5f + 15.0f;
+ } else if (dB < -20.0f) {
+ def = (dB + 30.0f) * 2.0f + 30.0f;
+ } else if (dB < 6.0f) {
+ def = (dB + 20.0f) * 2.5f + 50.0f;
+ } else {
+ def = 115.0f;
+ }
+
+ /* 115 is the deflection %age that would be
+ when dB=6.0. this is an arbitrary
+ endpoint for our scaling.
+ */
+
+ MidiByteArray msg;
+
+ if (def > 100.0f) {
+ if (!overload_on) {
+ overload_on = true;
+ surface.write (MidiByteArray (2, 0xd0, (id() << 4) | 0xe));
+ }
+ } else {
+ if (overload_on) {
+ overload_on = false;
+ surface.write (MidiByteArray (2, 0xd0, (id() << 4) | 0xf));
+ }
+ }
+
+ /* we can use up to 13 segments */
+
+ int segment = lrintf ((def/115.0) * 13.0);
+
+ //only send an update 'twice'
+ if (segment == last_update_segment) {
+ if (segment == llast_update_segment) {
+ return;
+ }
+ }
+ llast_update_segment = last_update_segment;
+ last_update_segment = segment;
+
+
+ surface.write (MidiByteArray (2, 0xd0, (id()<<4) | segment));
+}
+
+MidiByteArray
+Meter::zero ()
+{
+ return MidiByteArray (2, 0xD0, (id()<<4 | 0));
+}
diff --git a/libs/surfaces/us2400/meter.h b/libs/surfaces/us2400/meter.h
new file mode 100644
index 0000000000..0ef9e78c33
--- /dev/null
+++ b/libs/surfaces/us2400/meter.h
@@ -0,0 +1,66 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+
+ 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_us2400_control_protocol_meter_h__
+#define __ardour_us2400_control_protocol_meter_h__
+
+#include "controls.h"
+#include "midi_byte_array.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class SurfacePort;
+
+class Meter : public Control
+{
+public:
+ Meter (int id, std::string name, Group & group)
+ : Control (id, name, group)
+ , _enabled (false)
+ , overload_on (false),
+ last_update_segment (-1),
+ llast_update_segment (-1)
+ {}
+
+
+ void send_update (Surface&, float dB);
+ bool enabled () const { return _enabled; }
+
+ MidiByteArray zero();
+
+ static Control* factory (Surface&, int id, const char*, Group&);
+
+ void notify_metering_state_changed(Surface& surface, bool transport_is_rolling, bool metering_active);
+
+ private:
+ bool _enabled;
+ bool overload_on;
+
+ int last_update_segment;
+ int llast_update_segment;
+};
+
+} // US2400 namespace
+} // ArdourSurface namespace
+
+#endif /* __ardour_us2400_control_protocol_meter_h__ */
diff --git a/libs/surfaces/us2400/midi_byte_array.cc b/libs/surfaces/us2400/midi_byte_array.cc
new file mode 100644
index 0000000000..45d0439a75
--- /dev/null
+++ b/libs/surfaces/us2400/midi_byte_array.cc
@@ -0,0 +1,96 @@
+/*
+ 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 "midi_byte_array.h"
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+#include <cstdarg>
+#include <iomanip>
+#include <stdexcept>
+
+using namespace std;
+
+MidiByteArray::MidiByteArray (size_t size, MIDI::byte array[])
+ : std::vector<MIDI::byte>()
+{
+ for (size_t i = 0; i < size; ++i)
+ {
+ push_back (array[i]);
+ }
+}
+
+MidiByteArray::MidiByteArray (size_t count, MIDI::byte first, ...)
+ : vector<MIDI::byte>()
+{
+ push_back (first);
+ va_list var_args;
+ va_start (var_args, first);
+ for (size_t i = 1; i < count; ++i)
+ {
+ MIDI::byte b = va_arg (var_args, int);
+ push_back (b);
+ }
+ va_end (var_args);
+}
+
+
+void MidiByteArray::copy (size_t count, MIDI::byte * arr)
+{
+ for (size_t i = 0; i < count; ++i) {
+ push_back (arr[i]);
+ }
+}
+
+MidiByteArray & operator << (MidiByteArray & mba, const MIDI::byte & b)
+{
+ mba.push_back (b);
+ return mba;
+}
+
+MidiByteArray & operator << (MidiByteArray & mba, const MidiByteArray & barr)
+{
+ back_insert_iterator<MidiByteArray> bit (mba);
+ copy (barr.begin(), barr.end(), bit);
+ return mba;
+}
+
+ostream & operator << (ostream & os, const MidiByteArray & mba)
+{
+ os << "[";
+ char fill = os.fill('0');
+ for (MidiByteArray::const_iterator it = mba.begin(); it != mba.end(); ++it) {
+ if (it != mba.begin()) os << " ";
+ os << hex << setw(2) << (int)*it;
+ }
+ os.fill (fill);
+ os << dec;
+ os << "]";
+ return os;
+}
+
+MidiByteArray & operator << (MidiByteArray & mba, const std::string & st)
+{
+ /* note that this assumes that "st" is ASCII encoded
+ */
+
+ mba.insert (mba.end(), st.begin(), st.end());
+ return mba;
+}
diff --git a/libs/surfaces/us2400/midi_byte_array.h b/libs/surfaces/us2400/midi_byte_array.h
new file mode 100644
index 0000000000..3d3bcecd28
--- /dev/null
+++ b/libs/surfaces/us2400/midi_byte_array.h
@@ -0,0 +1,76 @@
+/*
+ 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 midi_byte_array_h
+#define midi_byte_array_h
+
+#include <iostream>
+#include <vector>
+
+#include <boost/shared_array.hpp>
+
+//#include <midi++/types.h>
+namespace MIDI {
+ typedef unsigned char byte;
+}
+
+/**
+ To make building arrays of bytes easier. Thusly:
+
+ MidiByteArray mba;
+ mba << 0xf0 << 0x00 << 0xf7;
+
+ MidiByteArray buf;
+ buf << mba;
+
+ MidiByteArray direct( 3, 0xf0, 0x00, 0xf7 );
+
+ cout << mba << endl;
+ cout << buf << endl;
+ cout << direct << endl;
+
+ will all result in "f0 00 f7" being output to stdout
+*/
+class MidiByteArray : public std::vector<MIDI::byte>
+{
+public:
+ MidiByteArray() : std::vector<MIDI::byte>() {}
+
+ MidiByteArray( size_t count, MIDI::byte array[] );
+
+ /**
+ Accepts a preceding count, and then a list of bytes
+ */
+ MidiByteArray( size_t count, MIDI::byte first, ... );
+
+ /// copy the given number of bytes from the given array
+ void copy( size_t count, MIDI::byte arr[] );
+};
+
+/// append the given byte to the end of the array
+MidiByteArray & operator << ( MidiByteArray & mba, const MIDI::byte & b );
+
+/// append the given string to the end of the array
+MidiByteArray & operator << ( MidiByteArray & mba, const std::string & );
+
+/// append the given array to the end of this array
+MidiByteArray & operator << ( MidiByteArray & mba, const MidiByteArray & barr );
+
+/// output the bytes as hex to the given stream
+std::ostream & operator << ( std::ostream & os, const MidiByteArray & mba );
+
+#endif
diff --git a/libs/surfaces/us2400/pot.cc b/libs/surfaces/us2400/pot.cc
new file mode 100644
index 0000000000..4391ea010f
--- /dev/null
+++ b/libs/surfaces/us2400/pot.cc
@@ -0,0 +1,86 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 ArdourSurface;
+using namespace US2400;
+
+int const Pot::External = 0x2e; /* specific ID for "vpot" representing external control */
+int const Pot::ID = 0x10; /* base value for v-pot IDs */
+
+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 (float val, bool onoff)
+{
+ int posi = lrintf (128.0 * val);
+ if (posi == last_update_position) {
+ if (posi == llast_update_position) {
+ return MidiByteArray();
+ }
+ }
+ llast_update_position = last_update_position;
+ last_update_position = posi;
+
+ // TODO do an exact calc for 0.50? To allow manually re-centering the port.
+
+ // center on if val is "very close" to 0.50
+ MIDI::byte msg = (val > 0.48 && val < 0.58 ? 1 : 0) << 6;
+
+ // Pot/LED mode
+ msg |= (_mode << 4);
+
+ /*
+ * Even though a width value may be negative, there is
+ * technically still width there, it is just reversed,
+ * so make sure to show it on the LED ring appropriately.
+ */
+ if (val < 0){
+ val = val * -1;
+ }
+
+ // val, but only if off hasn't explicitly been set
+ if (onoff) {
+ if (_mode == spread) {
+ msg |= (lrintf (val * 6)) & 0x0f; // 0b00001111
+ } else {
+ msg |= (lrintf (val * 10.0) + 1) & 0x0f; // 0b00001111
+ }
+ }
+
+ /* outbound LED message requires 0x20 to be added to the LED's id
+ */
+
+ return MidiByteArray (3, 0xb0, 0x20 + id(), msg);
+
+}
+
diff --git a/libs/surfaces/us2400/pot.h b/libs/surfaces/us2400/pot.h
new file mode 100644
index 0000000000..1ee1142fa1
--- /dev/null
+++ b/libs/surfaces/us2400/pot.h
@@ -0,0 +1,64 @@
+/*
+ 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_us2400_control_protocol_pot_h__
+#define __ardour_us2400_control_protocol_pot_h__
+
+#include "controls.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Pot : public Control
+{
+public:
+ static int const External;
+ static int const ID;
+
+ enum Mode {
+ dot = 0,
+ boost_cut = 1,
+ wrap = 2,
+ spread = 3
+ };
+
+ Pot (int id, std::string name, Group & group)
+ : Control (id, name, group),
+ last_update_position (-1),
+ llast_update_position (-1) {}
+
+ void set_mode(Mode m) {_mode = m; last_update_position = -1; }
+
+ MidiByteArray set (float, bool);
+ MidiByteArray zero() { return set (0.0, false); }
+
+ static Control* factory (Surface&, int id, const char*, Group&);
+
+ int last_update_position;
+ int llast_update_position;
+
+ Mode _mode;
+
+};
+
+}
+}
+
+#endif /* __ardour_us2400_control_protocol_pot_h__ */
diff --git a/libs/surfaces/us2400/strip.cc b/libs/surfaces/us2400/strip.cc
new file mode 100644
index 0000000000..aa186c0b38
--- /dev/null
+++ b/libs/surfaces/us2400/strip.cc
@@ -0,0 +1,970 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <sstream>
+#include <vector>
+#include <climits>
+
+#include <stdint.h>
+
+#include <sys/time.h>
+
+#include <glibmm/convert.h>
+
+#include "midi++/port.h"
+
+#include "pbd/compose.h"
+#include "pbd/convert.h"
+
+#include "ardour/amp.h"
+#include "ardour/bundle.h"
+#include "ardour/debug.h"
+#include "ardour/midi_ui.h"
+#include "ardour/meter.h"
+#include "ardour/monitor_control.h"
+#include "ardour/plugin_insert.h"
+#include "ardour/pannable.h"
+#include "ardour/panner.h"
+#include "ardour/panner_shell.h"
+#include "ardour/phase_control.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/record_enable_control.h"
+#include "ardour/route.h"
+#include "ardour/session.h"
+#include "ardour/send.h"
+#include "ardour/solo_isolate_control.h"
+#include "ardour/track.h"
+#include "ardour/midi_track.h"
+#include "ardour/user_bundle.h"
+#include "ardour/profile.h"
+#include "ardour/value_as_string.h"
+
+#include "us2400_control_protocol.h"
+#include "surface_port.h"
+#include "surface.h"
+#include "strip.h"
+#include "button.h"
+#include "led.h"
+#include "pot.h"
+#include "fader.h"
+#include "jog.h"
+#include "meter.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+using namespace ArdourSurface;
+using namespace US2400;
+
+#ifndef timeradd /// only avail with __USE_BSD
+#define timeradd(a,b,result) \
+ do { \
+ (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
+ (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
+ if ((result)->tv_usec >= 1000000) \
+ { \
+ ++(result)->tv_sec; \
+ (result)->tv_usec -= 1000000; \
+ } \
+ } while (0)
+#endif
+
+#define ui_context() US2400Protocol::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
+
+Strip::Strip (Surface& s, const std::string& name, int index, const map<Button::ID,StripButtonInfo>& strip_buttons)
+ : Group (name)
+ , _solo (0)
+ , _mute (0)
+ , _select (0)
+ , _fader_touch (0)
+ , _vpot (0)
+ , _fader (0)
+ , _meter (0)
+ , _index (index)
+ , _surface (&s)
+ , _controls_locked (false)
+ , _transport_is_rolling (false)
+ , _metering_active (true)
+ , _pan_mode (PanAzimuthAutomation)
+{
+ _fader = dynamic_cast<Fader*> (Fader::factory (*_surface, index, "fader", *this));
+ _vpot = dynamic_cast<Pot*> (Pot::factory (*_surface, Pot::ID + index, "vpot", *this));
+
+ if (s.mcp().device_info().has_meters()) {
+ _meter = dynamic_cast<Meter*> (Meter::factory (*_surface, index, "meter", *this));
+ }
+
+ for (map<Button::ID,StripButtonInfo>::const_iterator b = strip_buttons.begin(); b != strip_buttons.end(); ++b) {
+ Button* bb = dynamic_cast<Button*> (Button::factory (*_surface, b->first, b->second.base_id + index, b->second.name, *this));
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface %1 strip %2 new button BID %3 id %4 from base %5\n",
+ _surface->number(), index, Button::id_to_name (bb->bid()),
+ bb->id(), b->second.base_id));
+ }
+
+ _trickle_counter = 0;
+}
+
+Strip::~Strip ()
+{
+ /* surface is responsible for deleting all controls */
+}
+
+void
+Strip::add (Control & control)
+{
+ Button* button;
+
+ Group::add (control);
+
+ /* fader, vpot, meter were all set explicitly */
+
+ if ((button = dynamic_cast<Button*>(&control)) != 0) {
+ switch (button->bid()) {
+ case Button::Mute:
+ _mute = button;
+ break;
+ case Button::Solo:
+ _solo = button;
+ break;
+ case Button::Select:
+ _select = button;
+ break;
+ case Button::FaderTouch:
+ _fader_touch = button;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+Strip::set_stripable (boost::shared_ptr<Stripable> r, bool /*with_messages*/)
+{
+ if (_controls_locked) {
+ return;
+ }
+
+ stripable_connections.drop_connections ();
+
+ _solo->set_control (boost::shared_ptr<AutomationControl>());
+ _mute->set_control (boost::shared_ptr<AutomationControl>());
+ _select->set_control (boost::shared_ptr<AutomationControl>());
+
+ _fader->set_control (boost::shared_ptr<AutomationControl>());
+ _vpot->set_control (boost::shared_ptr<AutomationControl>());
+
+ _stripable = r;
+
+ reset_saved_values ();
+
+ if (!r) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface %1 Strip %2 mapped to null route\n", _surface->number(), _index));
+ zero ();
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface %1 strip %2 now mapping stripable %3\n",
+ _surface->number(), _index, _stripable->name()));
+
+ _solo->set_control (_stripable->solo_control());
+ _mute->set_control (_stripable->mute_control());
+
+ _stripable->solo_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_solo_changed, this), ui_context());
+ _stripable->mute_control()->Changed.connect(stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_mute_changed, this), ui_context());
+
+ boost::shared_ptr<AutomationControl> pan_control = _stripable->pan_azimuth_control();
+ if (pan_control) {
+ pan_control->Changed.connect(stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_panner_azi_changed, this, false), ui_context());
+ }
+
+ pan_control = _stripable->pan_width_control();
+ if (pan_control) {
+ pan_control->Changed.connect(stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_panner_width_changed, this, false), ui_context());
+ }
+
+ _stripable->gain_control()->Changed.connect(stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_gain_changed, this, false), ui_context());
+ _stripable->PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_property_changed, this, _1), ui_context());
+ _stripable->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_property_changed, this, _1), ui_context());
+
+ // TODO this works when a currently-banked stripable is made inactive, but not
+ // when a stripable is activated which should be currently banked.
+
+ _stripable->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_stripable_deleted, this), ui_context());
+
+ /* setup legal VPot modes for this stripable */
+
+ possible_pot_parameters.clear();
+
+ if (_stripable->pan_azimuth_control()) {
+ possible_pot_parameters.push_back (PanAzimuthAutomation);
+ }
+ if (_stripable->pan_width_control()) {
+ possible_pot_parameters.push_back (PanWidthAutomation);
+ }
+ if (_stripable->pan_elevation_control()) {
+ possible_pot_parameters.push_back (PanElevationAutomation);
+ }
+ if (_stripable->pan_frontback_control()) {
+ possible_pot_parameters.push_back (PanFrontBackAutomation);
+ }
+ if (_stripable->pan_lfe_control()) {
+ possible_pot_parameters.push_back (PanLFEAutomation);
+ }
+
+ _pan_mode = PanAzimuthAutomation;
+
+ if (_surface->mcp().subview_mode() == US2400Protocol::None) {
+ set_vpot_parameter (_pan_mode);
+ }
+
+ _fader->set_control (_stripable->gain_control());
+
+ notify_all ();
+}
+
+void
+Strip::reset_stripable ()
+{
+ stripable_connections.drop_connections ();
+
+ _solo->set_control (boost::shared_ptr<AutomationControl>());
+ _mute->set_control (boost::shared_ptr<AutomationControl>());
+ _select->set_control (boost::shared_ptr<AutomationControl>());
+
+ _fader->reset_control ();
+ _vpot->reset_control ();
+
+ _stripable.reset();
+
+ reset_saved_values ();
+
+ notify_all ();
+}
+
+
+void
+Strip::notify_all()
+{
+// if (!_stripable) {
+// zero ();
+// return;
+// }
+ // The active V-pot control may not be active for this strip
+ // But if we zero it in the controls function it may erase
+ // the one we do want
+// _surface->write (_vpot->zero());
+
+ notify_solo_changed ();
+ notify_mute_changed ();
+ notify_gain_changed ();
+ notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name));
+ notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::selected));
+ notify_panner_azi_changed ();
+ notify_vpot_change ();
+ notify_panner_width_changed ();
+ notify_record_enable_changed ();
+// notify_processor_changed ();
+}
+
+void
+Strip::notify_solo_changed ()
+{
+// if (_stripable && _solo) {
+// _surface->write (_solo->set_state (_stripable->solo_control()->soloed() ? on : off));
+// }
+
+ _trickle_counter = 0;
+}
+
+void
+Strip::notify_mute_changed ()
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Strip %1 mute changed\n", _index));
+// if (_stripable && _mute) {
+// DEBUG_TRACE (DEBUG::US2400, string_compose ("\tstripable muted ? %1\n", _stripable->mute_control()->muted()));
+// DEBUG_TRACE (DEBUG::US2400, string_compose ("mute message: %1\n", _mute->set_state (_stripable->mute_control()->muted() ? on : off)));
+//
+// _surface->write (_mute->set_state (_stripable->mute_control()->muted() ? on : off));
+// } else {
+// _surface->write (_mute->zero());
+// }
+
+ _trickle_counter = 0;
+}
+
+void
+Strip::notify_record_enable_changed ()
+{
+}
+
+void
+Strip::notify_stripable_deleted ()
+{
+ _surface->mcp().notify_stripable_removed ();
+ _surface->mcp().refresh_current_bank();
+}
+
+void
+Strip::notify_gain_changed (bool force_update)
+{
+ _trickle_counter = 0;
+}
+
+void
+Strip::notify_processor_changed (bool force_update)
+{
+}
+
+void
+Strip::notify_property_changed (const PropertyChange& what_changed)
+{
+}
+
+void
+Strip::update_selection_state ()
+{
+ _trickle_counter = 0;
+
+// if(_stripable) {
+// _surface->write (_select->set_state (_stripable->is_selected()));
+// }
+}
+
+void
+Strip::show_stripable_name ()
+{
+}
+
+void
+Strip::notify_vpot_change ()
+{
+ _trickle_counter = 0;
+}
+
+void
+Strip::notify_panner_azi_changed (bool force_update)
+{
+ _trickle_counter = 0;
+}
+
+void
+Strip::notify_panner_width_changed (bool force_update)
+{
+ _trickle_counter = 0;
+}
+
+void
+Strip::select_event (Button&, ButtonState bs)
+{
+ DEBUG_TRACE (DEBUG::US2400, "select button\n");
+
+ if (bs == press) {
+
+ int ms = _surface->mcp().main_modifier_state();
+
+ if (ms & US2400Protocol::MODIFIER_CMDALT) {
+ _controls_locked = !_controls_locked;
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, "add select button on press\n");
+ _surface->mcp().add_down_select_button (_surface->number(), _index);
+ _surface->mcp().select_range (_surface->mcp().global_index (*this));
+
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, "remove select button on release\n");
+ _surface->mcp().remove_down_select_button (_surface->number(), _index);
+ }
+}
+
+void
+Strip::vselect_event (Button&, ButtonState bs)
+{
+}
+
+void
+Strip::fader_touch_event (Button&, ButtonState bs)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("fader touch, press ? %1\n", (bs == press)));
+
+ if (bs == press) {
+
+ boost::shared_ptr<AutomationControl> ac = _fader->control ();
+
+ _fader->set_in_use (true);
+ _fader->start_touch (_surface->mcp().transport_frame());
+
+ } else {
+
+ _fader->set_in_use (false);
+ _fader->stop_touch (_surface->mcp().transport_frame());
+
+ }
+}
+
+
+void
+Strip::handle_button (Button& button, ButtonState bs)
+{
+ boost::shared_ptr<AutomationControl> control;
+
+ if (bs == press) {
+ button.set_in_use (true);
+ } else {
+ button.set_in_use (false);
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("strip %1 handling button %2 press ? %3\n", _index, button.bid(), (bs == press)));
+
+ switch (button.bid()) {
+ case Button::Select:
+ select_event (button, bs);
+ break;
+
+ case Button::FaderTouch:
+ fader_touch_event (button, bs);
+ break;
+
+ default:
+ if ((control = button.control ())) {
+ if (bs == press) {
+ DEBUG_TRACE (DEBUG::US2400, "add button on press\n");
+ _surface->mcp().add_down_button ((AutomationType) control->parameter().type(), _surface->number(), _index);
+
+ float new_value = control->get_value() ? 0.0 : 1.0;
+
+ /* get all controls that either have their
+ * button down or are within a range of
+ * several down buttons
+ */
+
+ US2400Protocol::ControlList controls = _surface->mcp().down_controls ((AutomationType) control->parameter().type(),
+ _surface->mcp().global_index(*this));
+
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("there are %1 buttons down for control type %2, new value = %3\n",
+ controls.size(), control->parameter().type(), new_value));
+
+ /* apply change, with potential modifier semantics */
+
+ Controllable::GroupControlDisposition gcd;
+
+ if (_surface->mcp().main_modifier_state() & US2400Protocol::MODIFIER_SHIFT) {
+ gcd = Controllable::InverseGroup;
+ } else {
+ gcd = Controllable::UseGroup;
+ }
+
+ for (US2400Protocol::ControlList::iterator c = controls.begin(); c != controls.end(); ++c) {
+ (*c)->set_value (new_value, gcd);
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, "remove button on release\n");
+ _surface->mcp().remove_down_button ((AutomationType) control->parameter().type(), _surface->number(), _index);
+ }
+ }
+ break;
+ }
+}
+
+
+void
+Strip::handle_fader_touch (Fader& fader, bool touch_on)
+{
+ if (touch_on) {
+ fader.start_touch (_surface->mcp().transport_frame());
+ } else {
+ fader.stop_touch (_surface->mcp().transport_frame());
+ }
+}
+
+void
+Strip::handle_fader (Fader& fader, float position)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("fader to %1\n", position));
+ boost::shared_ptr<AutomationControl> ac = fader.control();
+ if (!ac) {
+ return;
+ }
+
+ Controllable::GroupControlDisposition gcd = Controllable::UseGroup;
+
+ if (_surface->mcp().main_modifier_state() & US2400Protocol::MODIFIER_SHIFT) {
+ gcd = Controllable::InverseGroup;
+ }
+
+ fader.set_value (position, gcd);
+
+ /* From the Mackie Control MIDI implementation docs:
+
+ In order to ensure absolute synchronization with the host software,
+ Mackie Control uses a closed-loop servo system for the faders,
+ meaning the faders will always move to their last received position.
+ When a host receives a Fader Position Message, it must then
+ re-transmit that message to the Mackie Control or else the faders
+ will return to their last position.
+ */
+
+ _surface->write (fader.set_position (position));
+}
+
+void
+Strip::handle_pot (Pot& pot, float delta)
+{
+ /* Pots only emit events when they move, not when they
+ stop moving. So to get a stop event, we need to use a timeout.
+ */
+
+ boost::shared_ptr<AutomationControl> ac = pot.control();
+ if (!ac) {
+ return;
+ }
+
+ Controllable::GroupControlDisposition gcd;
+
+ if (_surface->mcp().main_modifier_state() & US2400Protocol::MODIFIER_SHIFT) {
+ gcd = Controllable::InverseGroup;
+ } else {
+ gcd = Controllable::UseGroup;
+ }
+
+ if (ac->toggled()) {
+
+ /* make it like a single-step, directional switch */
+
+ if (delta > 0) {
+ ac->set_value (1.0, gcd);
+ } else {
+ ac->set_value (0.0, gcd);
+ }
+
+ } else if (ac->desc().enumeration || ac->desc().integer_step) {
+
+ /* use Controllable::get_value() to avoid the
+ * "scaling-to-interface" that takes place in
+ * Control::get_value() via the pot member.
+ *
+ * an enumeration with 4 values will have interface values of
+ * 0.0, 0.25, 0.5 and 0.75 or some similar oddness. Lets not
+ * deal with that.
+ */
+
+ if (delta > 0) {
+ ac->set_value (min (ac->upper(), ac->get_value() + 1.0), gcd);
+ } else {
+ ac->set_value (max (ac->lower(), ac->get_value() - 1.0), gcd);
+ }
+
+ } else {
+
+ double p = ac->get_interface();
+
+ p += delta;
+
+ p = max (0.0, p);
+ p = min (1.0, p);
+
+ ac->set_value ( ac->interface_to_internal(p), gcd);
+ }
+}
+
+void
+Strip::periodic (ARDOUR::microseconds_t now)
+{
+
+ update_meter ();
+
+ if ( _trickle_counter %5 == 0 ) {
+
+ if ( _fader->control() ) {
+ _surface->write (_fader->set_position (_fader->control()->internal_to_interface (_fader->control()->get_value ())));
+ } else {
+ _surface->write (_fader->set_position(0.0));
+ }
+
+ if ( _vpot->control() ) {
+ _surface->write (_vpot->set (_vpot->control()->internal_to_interface (_vpot->control()->get_value ()), true));
+ } else {
+ _surface->write (_vpot->set(0.0, false));
+ }
+
+ if (_stripable) {
+ _surface->write (_solo->set_state (_stripable->solo_control()->soloed() ? on : off));
+ _surface->write (_mute->set_state (_stripable->mute_control()->muted() ? on : off));
+ _surface->write (_select->set_state (_stripable->is_selected()));
+ } else {
+ _surface->write (_solo->set_state (off));
+ _surface->write (_mute->set_state (off));
+ _surface->write (_select->set_state (off));
+ }
+
+ }
+ _trickle_counter++;
+}
+
+void
+Strip::redisplay (ARDOUR::microseconds_t now, bool force)
+{
+}
+
+void
+Strip::update_automation ()
+{
+}
+
+void
+Strip::update_meter ()
+{
+ if (!_stripable) {
+ return;
+ }
+
+ if (_meter && _transport_is_rolling && _metering_active && _stripable->peak_meter()) {
+ float dB = _stripable->peak_meter()->meter_level (0, MeterMCP);
+ _meter->send_update (*_surface, dB);
+ return;
+ }
+}
+
+void
+Strip::zero ()
+{
+ _trickle_counter = 0;
+}
+
+void
+Strip::lock_controls ()
+{
+ _controls_locked = true;
+}
+
+void
+Strip::unlock_controls ()
+{
+ _controls_locked = false;
+}
+
+string
+Strip::vpot_mode_string ()
+{
+ return "???";
+}
+
+void
+Strip::next_pot_mode ()
+{
+ vector<AutomationType>::iterator i;
+
+ boost::shared_ptr<AutomationControl> ac = _vpot->control();
+
+ if (!ac) {
+ return;
+ }
+
+
+ if (_surface->mcp().subview_mode() != US2400Protocol::None) {
+ return;
+ }
+
+ if (possible_pot_parameters.empty() || (possible_pot_parameters.size() == 1 && possible_pot_parameters.front() == ac->parameter().type())) {
+ return;
+ }
+
+ for (i = possible_pot_parameters.begin(); i != possible_pot_parameters.end(); ++i) {
+ if ((*i) == ac->parameter().type()) {
+ break;
+ }
+ }
+
+ /* move to the next mode in the list, or back to the start (which will
+ also happen if the current mode is not in the current pot mode list)
+ */
+
+ if (i != possible_pot_parameters.end()) {
+ ++i;
+ }
+
+ if (i == possible_pot_parameters.end()) {
+ i = possible_pot_parameters.begin();
+ }
+
+ set_vpot_parameter (*i);
+}
+
+void
+/*
+ *
+ * name: Strip::subview_mode_changed
+ * @param
+ * @return
+ *
+ */
+Strip::subview_mode_changed ()
+{
+ switch (_surface->mcp().subview_mode()) {
+
+ case US2400Protocol::None:
+ set_vpot_parameter (_pan_mode);
+ notify_metering_state_changed ();
+ break;
+
+ case US2400Protocol::TrackView:
+ boost::shared_ptr<Stripable> r = _surface->mcp().subview_stripable();
+ if (r) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose("subview_mode_changed strip %1:%2- assigning trackview pot\n", _surface->number(), _index));
+ setup_trackview_vpot (r);
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose("subview_mode_changed strip %1:%2 - no stripable\n", _surface->number(), _index));
+ }
+ break;
+
+ }
+
+ _trickle_counter = 0;
+}
+
+void
+Strip::setup_dyn_vpot (boost::shared_ptr<Stripable> r)
+{
+}
+
+void
+Strip::setup_eq_vpot (boost::shared_ptr<Stripable> r)
+{
+}
+
+void
+Strip::setup_sends_vpot (boost::shared_ptr<Stripable> r)
+{
+
+}
+
+void
+Strip::setup_trackview_vpot (boost::shared_ptr<Stripable> r)
+{
+ subview_connections.drop_connections ();
+
+ if (!r) {
+ return;
+ }
+
+ const uint32_t global_pos = _surface->mcp().global_index (*this);
+
+ boost::shared_ptr<AutomationControl> pc;
+ boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (r);
+ string label;
+
+ _vpot->set_mode(Pot::wrap);
+
+#ifdef MIXBUS
+ int eq_band = -1;
+ if (r->is_input_strip ()) {
+
+#ifdef MIXBUS32C
+ switch (global_pos) {
+ case 6:
+ pc = r->filter_freq_controllable(true);
+ break;
+ case 7:
+ pc = r->filter_freq_controllable(false);
+ break;
+ case 8:
+ case 10:
+ case 12:
+ case 14: {
+ eq_band = (global_pos-8) / 2;
+ pc = r->eq_freq_controllable (eq_band);
+ } break;
+ case 9:
+ case 11:
+ case 13:
+ case 15: {
+ eq_band = (global_pos-8) / 2;
+ pc = r->eq_gain_controllable (eq_band);
+ _vpot->set_mode(Pot::boost_cut);
+ } break;
+ }
+
+#else //regular Mixbus channel EQ
+
+ switch (global_pos) {
+ case 7:
+ pc = r->filter_freq_controllable(true);
+ break;
+ case 8:
+ case 10:
+ case 12:
+ eq_band = (global_pos-8) / 2;
+ pc = r->eq_gain_controllable (eq_band);
+ _vpot->set_mode(Pot::boost_cut);
+ break;
+ case 9:
+ case 11:
+ case 13:
+ eq_band = (global_pos-8) / 2;
+ pc = r->eq_freq_controllable (eq_band);
+ break;
+ }
+
+
+#endif
+
+ //trim & dynamics
+
+ switch (global_pos) {
+ case 0:
+ pc = r->trim_control ();
+ _vpot->set_mode(Pot::boost_cut);
+ break;
+
+ case 1:
+ pc = r->pan_azimuth_control ();
+ _vpot->set_mode(Pot::dot);
+ break;
+
+ case 2:
+ pc = r->comp_threshold_controllable();
+ break;
+
+ case 3:
+ pc = r->comp_speed_controllable();
+ break;
+
+ case 4:
+ pc = r->comp_mode_controllable();
+ _vpot->set_mode(Pot::wrap);
+ break;
+
+ case 5:
+ pc = r->comp_makeup_controllable();
+ break;
+
+
+ } //trim & dynamics
+
+ //mixbus sends
+ switch (global_pos) {
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ pc = r->send_level_controllable ( global_pos - 16 );
+ break;
+
+ } //global_pos switch
+
+ } //if input_strip
+#endif //ifdef MIXBUS
+
+ if (pc) { //control found; set our knob to watch for changes in it
+ _vpot->set_control (pc);
+ pc->Changed.connect (subview_connections, MISSING_INVALIDATOR, boost::bind (&Strip::notify_vpot_change, this), ui_context());
+ } else { //no control, just set the knob to "empty"
+ _vpot->reset_control ();
+ }
+
+ notify_vpot_change ();
+}
+
+void
+Strip::set_vpot_parameter (AutomationType p)
+{
+ if (!_stripable || (p == NullAutomation)) {
+ _vpot->set_control (boost::shared_ptr<AutomationControl>());
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> pan_control;
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("switch to vpot mode %1\n", p));
+
+ reset_saved_values ();
+
+ switch (p) {
+ case PanAzimuthAutomation:
+ pan_control = _stripable->pan_azimuth_control ();
+ break;
+ case PanWidthAutomation:
+ pan_control = _stripable->pan_width_control ();
+ break;
+ case PanElevationAutomation:
+ break;
+ case PanFrontBackAutomation:
+ break;
+ case PanLFEAutomation:
+ break;
+ default:
+ return;
+ }
+
+ if (pan_control) {
+ _pan_mode = p;
+ _vpot->set_mode (Pot::dot);
+ _vpot->set_control (pan_control);
+ }
+
+ notify_panner_azi_changed (true);
+}
+
+bool
+Strip::is_midi_track () const
+{
+ return boost::dynamic_pointer_cast<MidiTrack>(_stripable) != 0;
+}
+
+void
+Strip::reset_saved_values ()
+{
+}
+
+void
+Strip::notify_metering_state_changed()
+{
+ if (_surface->mcp().subview_mode() != US2400Protocol::None) {
+ return;
+ }
+
+ if (!_stripable || !_meter) {
+ return;
+ }
+
+ bool transport_is_rolling = (_surface->mcp().get_transport_speed () != 0.0f);
+ bool metering_active = _surface->mcp().metering_active ();
+
+ if ((_transport_is_rolling == transport_is_rolling) && (_metering_active == metering_active)) {
+ return;
+ }
+
+ _meter->notify_metering_state_changed (*_surface, transport_is_rolling, metering_active);
+
+ if (!transport_is_rolling || !metering_active) {
+ notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name));
+ notify_panner_azi_changed (true);
+ }
+
+ _transport_is_rolling = transport_is_rolling;
+ _metering_active = metering_active;
+}
diff --git a/libs/surfaces/us2400/strip.h b/libs/surfaces/us2400/strip.h
new file mode 100644
index 0000000000..0836657956
--- /dev/null
+++ b/libs/surfaces/us2400/strip.h
@@ -0,0 +1,158 @@
+#ifndef __ardour_us2400_control_protocol_strip_h__
+#define __ardour_us2400_control_protocol_strip_h__
+
+#include <string>
+#include <iostream>
+
+#include "evoral/Parameter.hpp"
+
+#include "pbd/property_basics.h"
+#include "pbd/ringbuffer.h"
+#include "pbd/signals.h"
+
+#include "ardour/types.h"
+#include "control_protocol/types.h"
+
+#include "control_group.h"
+#include "types.h"
+#include "us2400_control_protocol.h"
+#include "midi_byte_array.h"
+#include "device_info.h"
+
+namespace ARDOUR {
+ class Stripable;
+ class Bundle;
+ class ChannelCount;
+}
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+class Control;
+class Surface;
+class Button;
+class Pot;
+class Fader;
+class Meter;
+class SurfacePort;
+
+struct GlobalControlDefinition {
+ const char* name;
+ int id;
+ Control* (*factory)(Surface&, int index, const char* name, Group&);
+ const char* group_name;
+};
+
+/**
+ This is the set of controls that make up a strip.
+*/
+class Strip : public Group
+{
+public:
+ Strip (Surface&, const std::string & name, int index, const std::map<Button::ID,StripButtonInfo>&);
+ ~Strip();
+
+ boost::shared_ptr<ARDOUR::Stripable> stripable() const { return _stripable; }
+
+ void add (Control & control);
+ int index() const { return _index; } // zero based
+ Surface* surface() const { return _surface; }
+
+ void set_stripable (boost::shared_ptr<ARDOUR::Stripable>, bool with_messages = true);
+ void reset_stripable ();
+
+ // call all signal handlers manually
+ void notify_all ();
+
+ void handle_button (Button&, ButtonState bs);
+ void handle_fader (Fader&, float position);
+ void handle_fader_touch (Fader&, bool touch_on);
+ void handle_pot (Pot&, float delta);
+
+ void periodic (ARDOUR::microseconds_t now_usecs);
+ void redisplay (ARDOUR::microseconds_t now_usecs, bool force = true);
+
+ void zero ();
+
+ void subview_mode_changed ();
+
+ void lock_controls ();
+ void unlock_controls ();
+ bool locked() const { return _controls_locked; }
+
+ void notify_metering_state_changed();
+
+ void update_selection_state ();
+
+ int global_index() { return _surface->mcp().global_index (*this); }
+
+private:
+ enum VPotDisplayMode {
+ Name,
+ Value
+ };
+
+ Button* _solo;
+ Button* _mute;
+ Button* _select;
+ Button* _fader_touch;
+ Pot* _vpot;
+ Fader* _fader;
+ Meter* _meter;
+ int _index;
+ Surface* _surface;
+ bool _controls_locked;
+ bool _transport_is_rolling;
+ bool _metering_active;
+ boost::shared_ptr<ARDOUR::Stripable> _stripable;
+ PBD::ScopedConnectionList stripable_connections;
+ PBD::ScopedConnectionList subview_connections;
+ PBD::ScopedConnectionList send_connections;
+ int eq_band;
+
+ int _trickle_counter;
+
+ ARDOUR::AutomationType _pan_mode;
+
+ void notify_solo_changed ();
+ void notify_mute_changed ();
+ void notify_record_enable_changed ();
+ void notify_gain_changed (bool force_update = true);
+ void notify_property_changed (const PBD::PropertyChange&);
+ void notify_panner_azi_changed (bool force_update = true);
+ void notify_panner_width_changed (bool force_update = true);
+ void notify_stripable_deleted ();
+ void notify_processor_changed (bool force_update = true);
+ void update_automation ();
+ void update_meter ();
+ std::string vpot_mode_string ();
+
+ void next_pot_mode ();
+
+ void select_event (Button&, ButtonState);
+ void vselect_event (Button&, ButtonState);
+ void fader_touch_event (Button&, ButtonState);
+
+ std::vector<ARDOUR::AutomationType> possible_pot_parameters;
+ std::vector<ARDOUR::AutomationType> possible_trim_parameters;
+ void set_vpot_parameter (ARDOUR::AutomationType);
+ void show_stripable_name ();
+
+ void reset_saved_values ();
+
+ bool is_midi_track () const;
+
+ void notify_vpot_change ();
+
+ void setup_eq_vpot (boost::shared_ptr<ARDOUR::Stripable>);//
+ void setup_dyn_vpot (boost::shared_ptr<ARDOUR::Stripable>);//
+ void setup_sends_vpot (boost::shared_ptr<ARDOUR::Stripable>);//
+
+ void setup_trackview_vpot (boost::shared_ptr<ARDOUR::Stripable>);
+};
+
+}
+}
+
+#endif /* __ardour_us2400_control_protocol_strip_h__ */
diff --git a/libs/surfaces/us2400/surface.cc b/libs/surfaces/us2400/surface.cc
new file mode 100644
index 0000000000..0a2594a245
--- /dev/null
+++ b/libs/surfaces/us2400/surface.cc
@@ -0,0 +1,1090 @@
+/*
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <sstream>
+#include <iomanip>
+#include <iostream>
+#include <cstdio>
+#include <cmath>
+
+#include <glibmm/convert.h>
+
+#include "pbd/stacktrace.h"
+
+#include "midi++/port.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/automation_control.h"
+#include "ardour/debug.h"
+#include "ardour/route.h"
+#include "ardour/panner.h"
+#include "ardour/panner_shell.h"
+#include "ardour/profile.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/session.h"
+#include "ardour/utils.h"
+
+#include <gtkmm2ext/gui_thread.h>
+
+#include "control_group.h"
+#include "surface_port.h"
+#include "surface.h"
+#include "strip.h"
+#include "us2400_control_protocol.h"
+#include "jog_wheel.h"
+
+#include "strip.h"
+#include "button.h"
+#include "led.h"
+#include "pot.h"
+#include "fader.h"
+#include "jog.h"
+#include "meter.h"
+
+#include "pbd/i18n.h"
+
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
+
+using namespace std;
+using namespace PBD;
+using ARDOUR::Stripable;
+using ARDOUR::Panner;
+using ARDOUR::Profile;
+using ARDOUR::AutomationControl;
+using namespace ArdourSurface;
+using namespace US2400;
+
+#define ui_context() US2400Protocol::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
+
+// The MCU sysex header.4th byte Will be overwritten
+// when we get an incoming sysex that identifies
+// the device type
+static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x14);
+
+// The MCU extender sysex header.4th byte Will be overwritten
+// when we get an incoming sysex that identifies
+// the device type
+static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x15);
+
+static MidiByteArray empty_midi_byte_array;
+
+Surface::Surface (US2400Protocol& mcp, const std::string& device_name, uint32_t number, surface_type_t stype)
+ : _mcp (mcp)
+ , _stype (stype)
+ , _number (number)
+ , _name (device_name)
+ , _active (false)
+ , _connected (false)
+ , _jog_wheel (0)
+ , _master_fader (0)
+ , _last_master_gain_written (-0.0f)
+ , connection_state (0)
+ , input_source (0)
+{
+ DEBUG_TRACE (DEBUG::US2400, "Surface::Surface init\n");
+
+ try {
+ _port = new SurfacePort (*this);
+ } catch (...) {
+ throw failed_constructor ();
+ }
+
+ /* only the first Surface object has global controls */
+ /* lets use master_position instead */
+ uint32_t mp = _mcp.device_info().master_position();
+ if (_number == mp) {
+ DEBUG_TRACE (DEBUG::US2400, "Surface matches MasterPosition. Might have global controls.\n");
+ if (_mcp.device_info().has_global_controls()) {
+ init_controls ();
+ DEBUG_TRACE (DEBUG::US2400, "init_controls done\n");
+ }
+
+ if (_mcp.device_info().has_master_fader()) {
+ setup_master ();
+ DEBUG_TRACE (DEBUG::US2400, "setup_master done\n");
+ }
+ }
+
+ uint32_t n = _mcp.device_info().strip_cnt();
+
+ if (n) {
+ init_strips (n);
+ DEBUG_TRACE (DEBUG::US2400, "init_strips done\n");
+ }
+
+ connect_to_signals ();
+
+ DEBUG_TRACE (DEBUG::US2400, "Surface::Surface done\n");
+}
+
+Surface::~Surface ()
+{
+ DEBUG_TRACE (DEBUG::US2400, "Surface::~Surface init\n");
+
+ if (input_source) {
+ g_source_destroy (input_source);
+ input_source = 0;
+ }
+
+ // delete groups (strips)
+ for (Groups::iterator it = groups.begin(); it != groups.end(); ++it) {
+ delete it->second;
+ }
+
+ // delete controls (global buttons, master fader etc)
+ for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
+ delete *it;
+ }
+
+ delete _jog_wheel;
+ delete _port;
+ // the ports take time to release and we may be rebuilding right away
+ // in the case of changing devices.
+ g_usleep (10000);
+ DEBUG_TRACE (DEBUG::US2400, "Surface::~Surface done\n");
+}
+
+bool
+Surface::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+ if (!_port) {
+ return false;
+ }
+
+ string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (_port->input_name());
+ string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (_port->output_name());
+
+ if (ni == name1 || ni == name2) {
+ if (yn) {
+ connection_state |= InputConnected;
+ } else {
+ connection_state &= ~InputConnected;
+ }
+ } else if (no == name1 || no == name2) {
+ if (yn) {
+ connection_state |= OutputConnected;
+ } else {
+ connection_state &= ~OutputConnected;
+ }
+ } else {
+ /* not our ports */
+ return false;
+ }
+
+ if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+
+ /* this will send a device query message, which should
+ result in a response that will kick off device type
+ discovery and activation of the surface(s).
+
+ The intended order of events is:
+
+ - each surface sends a device query message
+ - devices respond with either MCP or LCP response (sysex in both
+ cases)
+ - sysex message causes Surface::turn_it_on() which tells the
+ MCP object that the surface is ready, and sets up strip
+ displays and binds faders and buttons for that surface
+
+ In the case of LCP, where this is a handshake process that could
+ fail, the response process to the initial sysex after a device query
+ will mark the surface inactive, which won't shut anything down
+ but will stop any writes to the device.
+
+ Note: there are no known cases of the handshake process failing.
+
+ We actually can't initiate this in this callback, so we have
+ to queue it with the MCP event loop.
+ */
+
+ /* XXX this is a horrible hack. Without a short sleep here,
+ something prevents the device wakeup messages from being
+ sent and/or the responses from being received.
+ */
+
+ g_usleep (100000);
+ connected ();
+
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface %1 disconnected (input or output or both)\n", _name));
+ _active = false;
+ }
+
+ return true; /* connection status changed */
+}
+
+XMLNode&
+Surface::get_state()
+{
+ XMLNode* node = new XMLNode (X_("Surface"));
+ node->set_property (X_("name"), _name);
+ node->add_child_nocopy (_port->get_state());
+ return *node;
+}
+
+int
+Surface::set_state (const XMLNode& node, int version)
+{
+ /* Look for a node named after the device we're part of */
+
+ XMLNodeList const& children = node.children();
+ XMLNode* mynode = 0;
+
+ for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
+ std::string name;
+ if ((*c)->get_property (X_("name"), name) && name == _name) {
+ mynode = *c;
+ break;
+ }
+ }
+
+ if (!mynode) {
+ return 0;
+ }
+
+ XMLNode* portnode = mynode->child (X_("Port"));
+ if (portnode) {
+ if (_port->set_state (*portnode, version)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+const MidiByteArray&
+Surface::sysex_hdr() const
+{
+ switch (_stype) {
+ case st_mcu: return mackie_sysex_hdr;
+ case st_ext: return mackie_sysex_hdr_xt;
+ default: return mackie_sysex_hdr_xt;
+ }
+ cout << "SurfacePort::sysex_hdr _port_type not known" << endl;
+ return mackie_sysex_hdr;
+}
+
+static GlobalControlDefinition mackie_global_controls[] = {
+ { "external", Pot::External, Pot::factory, "none" },
+ { "fader_touch", Led::FaderTouch, Led::factory, "master" },
+ { "timecode", Led::Timecode, Led::factory, "none" },
+ { "beats", Led::Beats, Led::factory, "none" },
+ { "solo", Led::RudeSolo, Led::factory, "none" },
+ { "relay_click", Led::RelayClick, Led::factory, "none" },
+ { "", 0, Led::factory, "" }
+};
+
+void
+Surface::init_controls()
+{
+ Group* group;
+
+ DEBUG_TRACE (DEBUG::US2400, "Surface::init_controls: creating groups\n");
+ groups["assignment"] = new Group ("assignment");
+ groups["automation"] = new Group ("automation");
+ groups["bank"] = new Group ("bank");
+ groups["cursor"] = new Group ("cursor");
+ groups["display"] = new Group ("display");
+ groups["function select"] = new Group ("function select");
+ groups["global view"] = new Group ("global view");
+ groups["master"] = new Group ("master");
+ groups["modifiers"] = new Group ("modifiers");
+ groups["none"] = new Group ("none");
+ groups["transport"] = new Group ("transport");
+ groups["user"] = new Group ("user");
+ groups["utilities"] = new Group ("utilities");
+
+ DEBUG_TRACE (DEBUG::US2400, "Surface::init_controls: creating jog wheel\n");
+ if (_mcp.device_info().has_jog_wheel()) {
+ _jog_wheel = new US2400::JogWheel (_mcp);
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, "Surface::init_controls: creating global controls\n");
+ for (uint32_t n = 0; mackie_global_controls[n].name[0]; ++n) {
+ group = groups[mackie_global_controls[n].group_name];
+ Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, mackie_global_controls[n].name, *group);
+ controls_by_device_independent_id[mackie_global_controls[n].id] = control;
+ }
+
+ /* add global buttons */
+ DEBUG_TRACE (DEBUG::US2400, "Surface::init_controls: adding global buttons\n");
+ const map<Button::ID,GlobalButtonInfo>& global_buttons (_mcp.device_info().global_buttons());
+
+ for (map<Button::ID,GlobalButtonInfo>::const_iterator b = global_buttons.begin(); b != global_buttons.end(); ++b){
+ group = groups[b->second.group];
+ controls_by_device_independent_id[b->first] = Button::factory (*this, b->first, b->second.id, b->second.label, *group);
+ }
+}
+
+void
+Surface::init_strips (uint32_t n)
+{
+ const map<Button::ID,StripButtonInfo>& strip_buttons (_mcp.device_info().strip_buttons());
+
+ //surface 4 has no strips
+ if ( (_stype != st_mcu) && (_stype != st_ext) )
+ return;
+
+ for (uint32_t i = 0; i < n; ++i) {
+
+ char name[32];
+
+ snprintf (name, sizeof (name), "strip_%d", (8* _number) + i);
+
+ Strip* strip = new Strip (*this, name, i, strip_buttons);
+
+ groups[name] = strip;
+ strips.push_back (strip);
+ }
+}
+
+void
+Surface::master_monitor_may_have_changed ()
+{
+ if (_number == _mcp.device_info().master_position()) {
+ setup_master ();
+ }
+}
+
+void
+Surface::setup_master ()
+{
+ boost::shared_ptr<Stripable> m;
+
+ if ((m = _mcp.get_session().monitor_out()) == 0) {
+ m = _mcp.get_session().master_out();
+ }
+
+ if (!m) {
+ if (_master_fader) {
+ _master_fader->reset_control ();
+ }
+ master_connection.disconnect ();
+ return;
+ }
+
+ if (!_master_fader) {
+ Groups::iterator group_it;
+ Group* master_group;
+ group_it = groups.find("master");
+
+ if (group_it == groups.end()) {
+ groups["master"] = master_group = new Group ("master");
+ } else {
+ master_group = group_it->second;
+ }
+
+ _master_fader = dynamic_cast<Fader*> (Fader::factory (*this, _mcp.device_info().strip_cnt(), "master", *master_group));
+
+ DeviceInfo device_info = _mcp.device_info();
+ GlobalButtonInfo master_button = device_info.get_global_button(Button::MasterFaderTouch);
+ Button* bb = dynamic_cast<Button*> (Button::factory (
+ *this,
+ Button::MasterFaderTouch,
+ master_button.id,
+ master_button.label,
+ *(group_it->second)
+ ));
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface %1 Master Fader new button BID %2 id %3\n",
+ number(), Button::MasterFaderTouch, bb->id()));
+ } else {
+ master_connection.disconnect ();
+ }
+
+ _master_fader->set_control (m->gain_control());
+ m->gain_control()->Changed.connect (master_connection, MISSING_INVALIDATOR, boost::bind (&Surface::master_gain_changed, this), ui_context());
+ _last_master_gain_written = FLT_MAX; /* some essentially impossible value */
+ master_gain_changed ();
+}
+
+void
+Surface::master_gain_changed ()
+{
+ if (!_master_fader) {
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> ac = _master_fader->control();
+ if (!ac) {
+ return;
+ }
+
+ float normalized_position = ac->internal_to_interface (ac->get_value());
+ if (normalized_position == _last_master_gain_written) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, "Surface::master_gain_changed: updating surface master fader\n");
+
+ _port->write (_master_fader->set_position (normalized_position));
+ _last_master_gain_written = normalized_position;
+}
+
+float
+Surface::scaled_delta (float delta, float current_speed)
+{
+ /* XXX needs work before use */
+ const float sign = delta < 0.0 ? -1.0 : 1.0;
+
+ return ((sign * std::pow (delta + 1.0, 2.0)) + current_speed) / 100.0;
+}
+
+void
+Surface::blank_jog_ring ()
+{
+}
+
+float
+Surface::scrub_scaling_factor () const
+{
+ return 100.0;
+}
+
+void
+Surface::connect_to_signals ()
+{
+ if (!_connected) {
+
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface %1 connecting to signals on port %2\n",
+ number(), _port->input_port().name()));
+
+ MIDI::Parser* p = _port->input_port().parser();
+
+ /* Incoming sysex */
+ p->sysex.connect_same_thread (*this, boost::bind (&Surface::handle_midi_sysex, this, _1, _2, _3));
+ /* V-Pot messages are Controller */
+ p->controller.connect_same_thread (*this, boost::bind (&Surface::handle_midi_controller_message, this, _1, _2));
+ /* Button messages are NoteOn */
+ p->note_on.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2));
+ /* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */
+ p->note_off.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2));
+ /* Fader messages are Pitchbend */
+ uint32_t i;
+ for (i = 0; i < _mcp.device_info().strip_cnt(); i++) {
+ p->channel_pitchbend[i].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, i));
+ }
+ // Master fader
+ p->channel_pitchbend[_mcp.device_info().strip_cnt()].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, _mcp.device_info().strip_cnt()));
+
+ _connected = true;
+ }
+}
+
+void
+Surface::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id)
+{
+ /* Pitchbend messages are fader position messages. Nothing in the data we get
+ * from the MIDI::Parser conveys the fader ID, which was given by the
+ * channel ID in the status byte.
+ *
+ * Instead, we have used bind() to supply the fader-within-strip ID
+ * when we connected to the per-channel pitchbend events.
+ */
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface::handle_midi_pitchbend_message on port %3, fader = %1 value = %2 (%4)\n",
+ fader_id, pb, _number, pb/16384.0));
+
+ turn_it_on ();
+
+ Fader* fader = faders[fader_id];
+
+ if (fader) {
+ Strip* strip = dynamic_cast<Strip*> (&fader->group());
+ float pos = pb / 16384.0;
+ if (strip) {
+ strip->handle_fader (*fader, pos);
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, "Handling master fader\n");
+ /* master fader */
+ fader->set_value (pos); // alter master gain
+ _port->write (fader->set_position (pos)); // write back value (required for servo)
+ }
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, "fader not found\n");
+ }
+}
+
+void
+Surface::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface::handle_midi_note_on_message %1 = %2\n", (int) ev->note_number, (int) ev->velocity));
+
+ turn_it_on ();
+
+ /* fader touch sense is given by "buttons" 0xe..0xe7 and 0xe8 for the
+ * master.
+ */
+
+ if (ev->note_number >= 0xE0 && ev->note_number <= 0xE8) {
+ Fader* fader = faders[ev->note_number];
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface: fader touch message, fader = %1\n", fader));
+
+ if (fader) {
+
+ Strip* strip = dynamic_cast<Strip*> (&fader->group());
+
+ if (ev->velocity > 64) {
+ strip->handle_fader_touch (*fader, true);
+ } else {
+ strip->handle_fader_touch (*fader, false);
+ }
+ }
+ return;
+ }
+
+ Button* button = buttons[ev->note_number];
+
+ if (button) {
+
+ if (ev->velocity > 64) {
+ button->pressed ();
+ }
+
+ Strip* strip = dynamic_cast<Strip*> (&button->group());
+
+ if (mcp().main_modifier_state() == US2400Protocol::MODIFIER_OPTION) {
+
+ /* special case: CLR Solo looks like a strip's solo button, but with MODIFIER_OPTION it becomes global CLR SOLO */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("HERE option global button %1\n", button->id()));
+ _mcp.handle_button_event (*this, *button, ev->velocity > 64 ? press : release);
+
+ } else if (strip) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("strip %1 button %2 pressed ? %3\n",
+ strip->index(), button->name(), (ev->velocity > 64)));
+ strip->handle_button (*button, ev->velocity > 64 ? press : release);
+ } else {
+ /* global button */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("global button %1\n", button->id()));
+ _mcp.handle_button_event (*this, *button, ev->velocity > 64 ? press : release);
+ }
+
+ if (ev->velocity <= 64) {
+ button->released ();
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("no button found for %1\n", (int) ev->note_number));
+ }
+
+ /* button release should reset timer AFTER handler(s) have run */
+}
+
+void
+Surface::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("SurfacePort::handle_midi_controller %1 = %2\n", (int) ev->controller_number, (int) ev->value));
+
+ turn_it_on ();
+
+#ifdef MIXBUS32C //in 32C, we can use the joystick for the last 2 mixbus send level & pans
+ if (_stype == st_joy) {
+ if ( ev->controller_number == 0x03 ) {
+ float value = (float)ev->value / 127.0;
+ float db_value = 20.0 * value;
+ float inv_db = 20.0 - db_value;
+ boost::shared_ptr<Stripable> r = mcp().subview_stripable();
+ if (r && r->is_input_strip() ) {
+ boost::shared_ptr<AutomationControl> pc = r->send_level_controllable ( 10 );
+ if (pc) {
+ pc->set_value( -db_value , PBD::Controllable::NoGroup );
+ }
+ pc = r->send_level_controllable ( 11 );
+ if (pc) {
+ pc->set_value( -inv_db, PBD::Controllable::NoGroup );
+ }
+ }
+ }
+ if ( ev->controller_number == 0x02 ) {
+ float value = (float)ev->value / 127.0;
+ boost::shared_ptr<Stripable> r = mcp().subview_stripable();
+ if (r && r->is_input_strip()) {
+ boost::shared_ptr<AutomationControl> pc = r->send_pan_azi_controllable ( 10 );
+ if (pc) {
+ float v = pc->interface_to_internal(value);
+ pc->set_value( v, PBD::Controllable::NoGroup );
+ }
+ pc = r->send_pan_azi_controllable ( 11 );
+ if (pc) {
+ float v = pc->interface_to_internal(value);
+ pc->set_value( v, PBD::Controllable::NoGroup );
+ }
+ }
+ }
+ return;
+ }
+#endif
+
+ Pot* pot = pots[ev->controller_number];
+
+ // bit 6 gives the sign
+ float sign = (ev->value & 0x40) == 0 ? 1.0 : -1.0;
+ // bits 0..5 give the velocity. we interpret this as "ticks
+ // moved before this message was sent"
+ float ticks = (ev->value & 0x3f);
+ if (ticks == 0) {
+ /* euphonix and perhaps other devices send zero
+ when they mean 1, we think.
+ */
+ ticks = 1;
+ }
+
+ float delta = 0;
+ if (mcp().main_modifier_state() == US2400Protocol::MODIFIER_SHIFT) {
+ delta = sign * (ticks / (float) 0xff);
+ } else {
+ delta = sign * (ticks / (float) 0x3f);
+ }
+
+ if (!pot) {
+ if (ev->controller_number == Jog::ID && _jog_wheel) {
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Jog wheel moved %1\n", ticks));
+ _jog_wheel->jog_event (delta);
+ return;
+ }
+ // add external (pedal?) control here
+
+ return;
+ }
+
+ Strip* strip = dynamic_cast<Strip*> (&pot->group());
+ if (strip) {
+ strip->handle_pot (*pot, delta);
+ }
+}
+
+void
+Surface::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
+{
+ MidiByteArray bytes (count, raw_bytes);
+
+ /* always save the device type ID so that our outgoing sysex messages
+ * are correct
+ */
+
+ if (_stype == st_mcu) {
+ mackie_sysex_hdr[4] = bytes[4];
+ } else {
+ mackie_sysex_hdr_xt[4] = bytes[4];
+ }
+
+ switch (bytes[5]) {
+ case 0x01:
+ if (!_active) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface #%1, handle_midi_sysex: %2\n", _number, bytes));
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Mackie Control Device ready, current status = %1\n", _active));
+ turn_it_on ();
+ }
+ break;
+
+ case 0x06:
+ if (!_active) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface #%1, handle_midi_sysex: %2\n", _number, bytes));
+ }
+ /* Behringer X-Touch Compact: Device Ready
+ */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Behringer X-Touch Compact ready, current status = %1\n", _active));
+ turn_it_on ();
+ break;
+
+ case 0x03: /* LCP Connection Confirmation */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface #%1, handle_midi_sysex: %2\n", _number, bytes));
+ DEBUG_TRACE (DEBUG::US2400, "Logic Control Device confirms connection, ardour replies\n");
+// if (bytes[4] == 0x10 || bytes[4] == 0x11) {
+// write_sysex (host_connection_confirmation (bytes)); turn_it_on ();
+ turn_it_on ();
+// }
+ break;
+
+// case 0x04: /* LCP: Confirmation Denied */
+// DEBUG_TRACE (DEBUG::US2400, string_compose ("surface #%1, handle_midi_sysex: %2\n", _number, bytes));
+// DEBUG_TRACE (DEBUG::US2400, "Logic Control Device denies connection\n");
+// _active = false;
+// break;
+
+ default:
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface #%1, handle_midi_sysex: %2\n", _number, bytes));
+// DEBUG_TRACE (DEBUG::US2400, string_compose ("unknown device ID byte %1", (int) bytes[5]));
+ error << "MCP: unknown sysex: " << bytes << endmsg;
+ }
+}
+
+static MidiByteArray
+calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
+{
+ MidiByteArray l;
+ back_insert_iterator<MidiByteArray> back (l);
+ copy (begin, end, back);
+
+ MidiByteArray retval;
+
+ // this is how to calculate the response to the challenge.
+ // from the Logic docs.
+ retval << (0x7f & (l[0] + (l[1] ^ 0xa) - l[3]));
+ retval << (0x7f & ( (l[2] >> l[3]) ^ (l[0] + l[3])));
+ retval << (0x7f & ((l[3] - (l[2] << 2)) ^ (l[0] | l[1])));
+ retval << (0x7f & (l[1] - l[2] + (0xf0 ^ (l[3] << 4))));
+
+ return retval;
+}
+
+MidiByteArray
+Surface::host_connection_query (MidiByteArray & bytes)
+{
+ MidiByteArray response;
+
+ if (bytes[4] != 0x10 && bytes[4] != 0x11) {
+ /* not a Logic Control device - no response required */
+ return response;
+ }
+
+ // handle host connection query
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("host connection query: %1\n", bytes));
+
+ if (bytes.size() != 18) {
+ cerr << "expecting 18 bytes, read " << bytes << " from " << _port->input_port().name() << endl;
+ return response;
+ }
+
+ // build and send host connection reply
+ response << 0x02;
+ copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response));
+ response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4);
+ return response;
+}
+
+MidiByteArray
+Surface::host_connection_confirmation (const MidiByteArray & bytes)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("host_connection_confirmation: %1\n", bytes));
+
+ // decode host connection confirmation
+ if (bytes.size() != 14) {
+ ostringstream os;
+ os << "expecting 14 bytes, read " << bytes << " from " << _port->input_port().name();
+ throw MackieControlException (os.str());
+ }
+
+ // send version request
+ return MidiByteArray (2, 0x13, 0x00);
+}
+
+void
+Surface::turn_it_on ()
+{
+ if (_active) {
+ return;
+ }
+
+ _active = true;
+
+ if ( _stype == st_mcu ) //do this once, when we hear from the master. this sets up current bank, etc
+ _mcp.device_ready ();
+
+ for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
+ (*s)->notify_all ();
+ }
+
+}
+
+void
+Surface::write_sysex (const MidiByteArray & mba)
+{
+ if (mba.empty()) {
+ return;
+ }
+
+ MidiByteArray buf;
+ buf << sysex_hdr() << mba << MIDI::eox;
+ _port->write (buf);
+}
+
+void
+Surface::write_sysex (MIDI::byte msg)
+{
+ MidiByteArray buf;
+ buf << sysex_hdr() << msg << MIDI::eox;
+ _port->write (buf);
+}
+
+uint32_t
+Surface::n_strips (bool with_locked_strips) const
+{
+ if (with_locked_strips) {
+ return strips.size();
+ }
+
+ uint32_t n = 0;
+
+ for (Strips::const_iterator it = strips.begin(); it != strips.end(); ++it) {
+ if (!(*it)->locked()) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+Strip*
+Surface::nth_strip (uint32_t n) const
+{
+ if (n > n_strips()) {
+ return 0;
+ }
+ return strips[n];
+}
+
+void
+Surface::zero_all ()
+{
+ if (_mcp.device_info().has_master_fader () && _master_fader) {
+ _port->write (_master_fader->zero ());
+ }
+
+ // zero all strips
+ for (Strips::iterator it = strips.begin(); it != strips.end(); ++it) {
+ (*it)->zero();
+ }
+
+ zero_controls ();
+}
+
+void
+Surface::zero_controls ()
+{
+ if (!_mcp.device_info().has_global_controls()) {
+ return;
+ }
+
+ // turn off global buttons and leds
+
+ for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
+ Control & control = **it;
+ if (!control.group().is_strip()) {
+ _port->write (control.zero());
+ }
+ }
+
+ // and the led ring for the master strip
+ blank_jog_ring ();
+
+ _last_master_gain_written = 0.0f;
+}
+
+void
+Surface::periodic (uint64_t now_usecs)
+{
+ master_gain_changed();
+ for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
+ (*s)->periodic (now_usecs);
+ }
+}
+
+void
+Surface::redisplay (ARDOUR::microseconds_t now, bool force)
+{
+ for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
+ (*s)->redisplay (now, force);
+ }
+}
+
+void
+Surface::write (const MidiByteArray& data)
+{
+ if (_active) {
+ _port->write (data);
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, "surface not active, write ignored\n");
+ }
+}
+
+void
+Surface::update_strip_selection ()
+{
+ Strips::iterator s = strips.begin();
+ for ( ; s != strips.end(); ++s) {
+ (*s)->update_selection_state();
+ }
+}
+
+void
+Surface::map_stripables (const vector<boost::shared_ptr<Stripable> >& stripables)
+{
+ vector<boost::shared_ptr<Stripable> >::const_iterator r;
+ Strips::iterator s = strips.begin();
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Mapping %1 stripables to %2 strips\n", stripables.size(), strips.size()));
+
+ for (r = stripables.begin(); r != stripables.end() && s != strips.end(); ++s) {
+
+ /* don't try to assign stripables to a locked strip. it won't
+ use it anyway, but if we do, then we get out of sync
+ with the proposed mapping.
+ */
+
+ if (!(*s)->locked()) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Mapping stripable \"%1\" to strip %2\n", (*r)->name(), (*s)->global_index() ));
+ (*s)->set_stripable (*r);
+ ++r;
+ }
+ }
+
+ for (; s != strips.end(); ++s) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("strip %1 being set to null stripable\n", (*s)->global_index()));
+ (*s)->reset_stripable ();
+ }
+}
+
+void
+Surface::subview_mode_changed ()
+{
+ for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
+ (*s)->subview_mode_changed ();
+ }
+}
+
+void
+Surface::say_hello ()
+{
+ /* wakeup for Mackie Control */
+ MidiByteArray wakeup (7, MIDI::sysex, 0x00, 0x00, 0x66, 0x14, 0x00, MIDI::eox);
+ _port->write (wakeup);
+ wakeup[4] = 0x15; /* wakup Mackie XT */
+ _port->write (wakeup);
+ wakeup[4] = 0x10; /* wakeup Logic Control */
+ _port->write (wakeup);
+ wakeup[4] = 0x11; /* wakeup Logic Control XT */
+ _port->write (wakeup);
+}
+
+void
+Surface::next_jog_mode ()
+{
+}
+
+void
+Surface::set_jog_mode (JogWheel::Mode)
+{
+}
+
+bool
+Surface::stripable_is_locked_to_strip (boost::shared_ptr<Stripable> stripable) const
+{
+ for (Strips::const_iterator s = strips.begin(); s != strips.end(); ++s) {
+ if ((*s)->stripable() == stripable && (*s)->locked()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+Surface::stripable_is_mapped (boost::shared_ptr<Stripable> stripable) const
+{
+ for (Strips::const_iterator s = strips.begin(); s != strips.end(); ++s) {
+ if ((*s)->stripable() == stripable) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+Surface::notify_metering_state_changed()
+{
+ for (Strips::const_iterator s = strips.begin(); s != strips.end(); ++s) {
+ (*s)->notify_metering_state_changed ();
+ }
+}
+
+void
+Surface::reset ()
+{
+ if (_port) {
+ /* reset msg for Mackie Control */
+ MidiByteArray msg;
+ msg << sysex_hdr();
+ msg << 0x08;
+ msg << 0x00;
+ msg << MIDI::eox;
+ _port->write (msg);
+ }
+}
+
+void
+Surface::toggle_backlight ()
+{
+return; //avoid sending anything that might be misconstrued
+}
+
+void
+Surface::recalibrate_faders ()
+{
+return; //avoid sending anything that might be misconstrued
+}
+
+void
+Surface::set_touch_sensitivity (int sensitivity)
+{
+ /* NOTE: assumed called from GUI code, hence sleep() */
+
+ /* sensitivity already clamped by caller */
+
+ if (_port) {
+ MidiByteArray msg;
+
+ msg << sysex_hdr ();
+ msg << 0x0e;
+ msg << 0xff; /* overwritten for each fader below */
+ msg << (sensitivity & 0x7f);
+ msg << MIDI::eox;
+
+ for (int fader = 0; fader < 9; ++fader) {
+ msg[6] = fader;
+ _port->write (msg);
+ }
+ }
+}
+
+void
+Surface::hui_heartbeat ()
+{
+ if (!_port) {
+ return;
+ }
+
+ MidiByteArray msg (3, MIDI::on, 0x0, 0x0);
+ _port->write (msg);
+}
+
+void
+Surface::connected ()
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Surface %1 now connected, trying to ping device...\n", _name));
+
+ say_hello ();
+}
+
+
diff --git a/libs/surfaces/us2400/surface.h b/libs/surfaces/us2400/surface.h
new file mode 100644
index 0000000000..e17bacb8d4
--- /dev/null
+++ b/libs/surfaces/us2400/surface.h
@@ -0,0 +1,204 @@
+#ifndef mackie_surface_h
+#define mackie_surface_h
+
+#include <stdint.h>
+
+#include <sigc++/trackable.h>
+
+#include "pbd/signals.h"
+#include "pbd/xml++.h"
+#include "midi++/types.h"
+
+#include "ardour/types.h"
+
+#include "control_protocol/types.h"
+
+#include "controls.h"
+#include "types.h"
+#include "jog_wheel.h"
+
+namespace MIDI {
+ class Parser;
+}
+
+namespace ARDOUR {
+ class Stripable;
+ class Port;
+}
+
+class MidiByteArray;
+
+namespace ArdourSurface {
+
+class US2400Protocol;
+
+namespace US2400
+{
+
+class MackieButtonHandler;
+class SurfacePort;
+class MackieMidiBuilder;
+class Button;
+class Meter;
+class Fader;
+class Jog;
+class Pot;
+class Led;
+
+class Surface : public PBD::ScopedConnectionList, public sigc::trackable
+{
+public:
+ Surface (US2400Protocol&, const std::string& name, uint32_t number, surface_type_t stype);
+ virtual ~Surface();
+
+ surface_type_t type() const { return _stype; }
+ uint32_t number() const { return _number; }
+ const std::string& name() { return _name; }
+
+ void connected ();
+
+ bool active() const { return _active; }
+
+ typedef std::vector<Control*> Controls;
+ Controls controls;
+
+ std::map<int,Fader*> faders;
+ std::map<int,Pot*> pots;
+ std::map<int,Button*> buttons; // index is device-DEPENDENT
+ std::map<int,Led*> leds;
+ std::map<int,Meter*> meters;
+ std::map<int,Control*> controls_by_device_independent_id;
+
+ US2400::JogWheel* jog_wheel() const { return _jog_wheel; }
+ Fader* master_fader() const { return _master_fader; }
+
+ /// The collection of all numbered strips.
+ typedef std::vector<Strip*> Strips;
+ Strips strips;
+
+ uint32_t n_strips (bool with_locked_strips = true) const;
+ Strip* nth_strip (uint32_t n) const;
+
+ bool stripable_is_locked_to_strip (boost::shared_ptr<ARDOUR::Stripable>) const;
+ bool stripable_is_mapped (boost::shared_ptr<ARDOUR::Stripable>) const;
+
+ /// This collection owns the groups
+ typedef std::map<std::string,Group*> Groups;
+ Groups groups;
+
+ SurfacePort& port() const { return *_port; }
+
+ void map_stripables (const std::vector<boost::shared_ptr<ARDOUR::Stripable> >&);
+
+ void update_strip_selection ();
+
+ const MidiByteArray& sysex_hdr() const;
+
+ void periodic (ARDOUR::microseconds_t now_usecs);
+ void redisplay (ARDOUR::microseconds_t now_usecs, bool force);
+ void hui_heartbeat ();
+
+ void handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t, uint32_t channel_id);
+ void handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes*);
+ void handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes*);
+
+ /// Connect the any signal from the parser to handle_midi_any
+ /// unless it's already connected
+ void connect_to_signals ();
+
+ /// write a sysex message
+ void write_sysex (const MidiByteArray& mba);
+ void write_sysex (MIDI::byte msg);
+ /// proxy write for port
+ void write (const MidiByteArray&);
+
+ /// display an indicator of the first switched-in Route. Do nothing by default.
+ void display_bank_start (uint32_t /*current_bank*/);
+
+ /// called from US2400Protocol::zero_all to turn things off
+ void zero_all ();
+ void zero_controls ();
+
+ /// turn off leds around the jog wheel. This is for surfaces that use a pot
+ /// pretending to be a jog wheel.
+ void blank_jog_ring ();
+
+ /// sends MCP "reset" message to surface
+ void reset ();
+
+ void recalibrate_faders ();
+ void toggle_backlight ();
+ void set_touch_sensitivity (int);
+
+ /**
+ This is used to calculate the clicks per second that define
+ a transport speed of 1.0 for the jog wheel. 100.0 is 10 clicks
+ per second, 50.5 is 5 clicks per second.
+ */
+ float scrub_scaling_factor() const;
+
+ /**
+ The scaling factor function for speed increase and decrease. At
+ low transport speeds this should return a small value, for high transport
+ speeds, this should return an exponentially larger value. This provides
+ high definition control at low speeds and quick speed changes to/from
+ higher speeds.
+ */
+ float scaled_delta (float delta, float current_speed);
+
+ void subview_mode_changed ();
+
+ US2400Protocol& mcp() const { return _mcp; }
+
+ void next_jog_mode ();
+ void set_jog_mode (US2400::JogWheel::Mode);
+
+ void notify_metering_state_changed();
+ void turn_it_on ();
+
+ bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool);
+
+ void master_monitor_may_have_changed ();
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ private:
+ US2400Protocol& _mcp;
+ SurfacePort* _port;
+ surface_type_t _stype;
+ uint32_t _number;
+ std::string _name;
+ bool _active;
+ bool _connected;
+ US2400::JogWheel* _jog_wheel;
+ Fader* _master_fader;
+ float _last_master_gain_written;
+ PBD::ScopedConnection master_connection;
+
+ void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
+ MidiByteArray host_connection_query (MidiByteArray& bytes);
+ MidiByteArray host_connection_confirmation (const MidiByteArray& bytes);
+
+ void say_hello ();
+ void init_controls ();
+ void init_strips (uint32_t n);
+ void setup_master ();
+ void master_gain_changed ();
+
+ enum ConnectionState {
+ InputConnected = 0x1,
+ OutputConnected = 0x2
+ };
+
+ int connection_state;
+
+ public:
+ /* IP MIDI devices need to keep a handle on this and destroy it */
+ GSource* input_source;
+};
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/surface_port.cc b/libs/surfaces/us2400/surface_port.cc
new file mode 100644
index 0000000000..88c1df11c9
--- /dev/null
+++ b/libs/surfaces/us2400/surface_port.cc
@@ -0,0 +1,201 @@
+/*
+ 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 <sstream>
+#include <cstring>
+#include <cerrno>
+
+#include <sigc++/sigc++.h>
+#include <boost/shared_array.hpp>
+
+#include "pbd/failed_constructor.h"
+
+#include "midi++/types.h"
+
+#include "ardour/async_midi_port.h"
+#include "ardour/debug.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/session.h"
+#include "ardour/audioengine.h"
+
+#include "controls.h"
+#include "us2400_control_protocol.h"
+#include "surface.h"
+#include "surface_port.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace US2400;
+
+SurfacePort::SurfacePort (Surface& s)
+ : _surface (&s)
+{
+ string in_name;
+ string out_name;
+
+ in_name = string_compose (X_("US-2400 In #%1"), (_surface->number() + 1));
+ out_name = string_compose (X_("US-2400 Out #%1"), _surface->number() + 1);
+
+ _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, in_name, true);
+ _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, out_name, true);
+
+ if (_async_in == 0 || _async_out == 0) {
+ throw failed_constructor();
+ }
+
+ _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
+ _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
+}
+
+SurfacePort::~SurfacePort()
+{
+ if (_async_in) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("unregistering input port %1\n", _async_in->name()));
+ Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
+ AudioEngine::instance()->unregister_port (_async_in);
+ _async_in.reset ((ARDOUR::Port*) 0);
+ }
+
+ if (_async_out) {
+ _output_port->drain (10000, 250000);
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("unregistering output port %1\n", _async_out->name()));
+ Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
+ AudioEngine::instance()->unregister_port (_async_out);
+ _async_out.reset ((ARDOUR::Port*) 0);
+ }
+}
+
+XMLNode&
+SurfacePort::get_state ()
+{
+ XMLNode* node = new XMLNode (X_("Port"));
+
+ XMLNode* child;
+
+ child = new XMLNode (X_("Input"));
+ child->add_child_nocopy (_async_in->get_state());
+ node->add_child_nocopy (*child);
+
+
+ child = new XMLNode (X_("Output"));
+ child->add_child_nocopy (_async_out->get_state());
+ node->add_child_nocopy (*child);
+
+ return *node;
+}
+
+int
+SurfacePort::set_state (const XMLNode& node, int version)
+{
+ XMLNode* child;
+
+ if ((child = node.child (X_("Input"))) != 0) {
+ XMLNode* portnode = child->child (Port::state_node_name.c_str());
+ if (portnode) {
+ _async_in->set_state (*portnode, version);
+ }
+ }
+
+ if ((child = node.child (X_("Output"))) != 0) {
+ XMLNode* portnode = child->child (Port::state_node_name.c_str());
+ if (portnode) {
+ _async_out->set_state (*portnode, version);
+ }
+ }
+
+ return 0;
+}
+
+void
+SurfacePort::reconnect ()
+{
+ _async_out->reconnect ();
+ _async_in->reconnect ();
+}
+
+std::string
+SurfacePort::input_name () const
+{
+ return _async_in->name();
+}
+
+std::string
+SurfacePort::output_name () const
+{
+ return _async_out->name();
+}
+
+// wrapper for one day when strerror_r is working properly
+string fetch_errmsg (int error_number)
+{
+ char * msg = strerror (error_number);
+ return msg;
+}
+
+int
+SurfacePort::write (const MidiByteArray & mba)
+{
+ if (mba.empty()) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("port %1 asked to write an empty MBA\n", output_port().name()));
+ return 0;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("port %1 write %2\n", output_port().name(), mba));
+
+ if (mba[0] != 0xf0 && mba.size() > 3) {
+ std::cerr << "TOO LONG WRITE: " << mba << std::endl;
+ }
+
+ /* this call relies on std::vector<T> using contiguous storage. not
+ * actually guaranteed by the standard, but way, way beyond likely.
+ */
+
+ int count = output_port().write (&mba[0], mba.size(), 0);
+
+ if (count != (int) mba.size()) {
+
+ if (errno == 0) {
+
+ cout << "port overflow on " << output_port().name() << ". Did not write all of " << mba << endl;
+
+ } else if (errno != EAGAIN) {
+ ostringstream os;
+ os << "Surface: couldn't write to port " << output_port().name();
+ os << ", error: " << fetch_errmsg (errno) << "(" << errno << ")";
+ cout << os.str() << endl;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+ostream &
+US2400::operator << (ostream & os, const SurfacePort & port)
+{
+ os << "{ ";
+ os << "name: " << port.input_port().name() << " " << port.output_port().name();
+ os << "; ";
+ os << " }";
+ return os;
+}
diff --git a/libs/surfaces/us2400/surface_port.h b/libs/surfaces/us2400/surface_port.h
new file mode 100644
index 0000000000..3e8c507a48
--- /dev/null
+++ b/libs/surfaces/us2400/surface_port.h
@@ -0,0 +1,91 @@
+/*
+ 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 surface_port_h
+#define surface_port_h
+
+#include <midi++/types.h>
+
+#include "pbd/signals.h"
+
+
+#include "midi_byte_array.h"
+#include "types.h"
+
+namespace MIDI {
+ class Parser;
+ class Port;
+}
+
+
+namespace ARDOUR {
+ class AsyncMIDIPort;
+ class Port;
+}
+
+namespace ArdourSurface {
+
+class US2400Protocol;
+
+namespace US2400
+{
+
+class Surface;
+
+/**
+ Make a relationship between a midi port and a Mackie device.
+*/
+
+class SurfacePort
+{
+ public:
+ SurfacePort (US2400::Surface&);
+ virtual ~SurfacePort();
+
+ /// an easier way to output bytes via midi
+ int write (const MidiByteArray&);
+
+ MIDI::Port& input_port() const { return *_input_port; }
+ MIDI::Port& output_port() const { return *_output_port; }
+
+ ARDOUR::Port& input() const { return *_async_in; }
+ ARDOUR::Port& output() const { return *_async_out; }
+
+ std::string input_name() const;
+ std::string output_name() const;
+
+ void reconnect ();
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ protected:
+
+ private:
+ US2400::Surface* _surface;
+ MIDI::Port* _input_port;
+ MIDI::Port* _output_port;
+ boost::shared_ptr<ARDOUR::Port> _async_in;
+ boost::shared_ptr<ARDOUR::Port> _async_out;
+};
+
+std::ostream& operator << (std::ostream& , const SurfacePort& port);
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/test.cc b/libs/surfaces/us2400/test.cc
new file mode 100644
index 0000000000..351058523f
--- /dev/null
+++ b/libs/surfaces/us2400/test.cc
@@ -0,0 +1,25 @@
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+#include <cstdarg>
+#include <iomanip>
+
+#include "midi_byte_array.h"
+
+using namespace std;
+
+namespace MIDI {
+ typedef unsigned char byte;
+ byte sysex = 0xf0;
+ byte eox = 0xf7;
+}
+
+int main()
+{
+ MidiByteArray bytes( 4, 0xf0, 0x01, 0x03, 0x7f );
+ cout << bytes << endl;
+ return 0;
+}
+
diff --git a/libs/surfaces/us2400/timer.h b/libs/surfaces/us2400/timer.h
new file mode 100644
index 0000000000..9b5d8d301b
--- /dev/null
+++ b/libs/surfaces/us2400/timer.h
@@ -0,0 +1,104 @@
+/*
+ Copyright (C) 1998, 1999, 2000, 2007 John Anderson
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library 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 Library General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef timer_h
+#define timer_h
+
+#ifdef _WIN32
+#include "windows.h"
+#else
+#include <sys/time.h>
+#endif
+
+namespace ArdourSurface {
+
+namespace US2400
+{
+
+/**
+ millisecond timer class.
+*/
+class Timer
+{
+public:
+
+ /**
+ start the timer running if true, or just create the
+ object if false.
+ */
+ Timer( bool shouldStart = true )
+ {
+ if ( shouldStart )
+ start();
+ }
+
+ /**
+ Start the timer running. Return the current timestamp, in milliseconds
+ */
+ unsigned long start()
+ {
+ _start = g_get_monotonic_time();
+ return _start / 1000;
+ }
+
+ /**
+ returns the number of milliseconds since start
+ also stops the timer running
+ */
+ unsigned long stop()
+ {
+ _stop = g_get_monotonic_time();
+ return elapsed();
+ }
+
+ /**
+ returns the number of milliseconds since start
+ */
+ unsigned long elapsed() const
+ {
+ if ( running )
+ {
+ uint64_t now = g_get_monotonic_time();
+ return (now - _start) / 1000;
+ }
+ else
+ {
+ return (_stop - _start) / 1000;
+ }
+ }
+
+ /**
+ Call stop and then start. Return the value from stop.
+ */
+ unsigned long restart()
+ {
+ unsigned long retval = stop();
+ start();
+ return retval;
+ }
+
+private:
+ uint64_t _start;
+ uint64_t _stop;
+ bool running;
+};
+
+} // US2400 namespace
+} // ArdourSurface namespace
+
+#endif
diff --git a/libs/surfaces/us2400/types.cc b/libs/surfaces/us2400/types.cc
new file mode 100644
index 0000000000..dca24fbac7
--- /dev/null
+++ b/libs/surfaces/us2400/types.cc
@@ -0,0 +1,51 @@
+/*
+ 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 "types.h"
+
+namespace ArdourSurface {
+
+namespace US2400 {
+
+LedState on( LedState::on );
+LedState off( LedState::off );
+LedState flashing( LedState::flashing );
+LedState none( LedState::none );
+
+}
+}
+
+std::ostream & operator << ( std::ostream & os, const ArdourSurface::US2400::ControlState & cs )
+{
+ os << "ControlState { ";
+ os << "pos: " << cs.pos;
+ os << ", ";
+ os << "sign: " << cs.sign;
+ os << ", ";
+ os << "delta: " << cs.delta;
+ os << ", ";
+ os << "ticks: " << cs.ticks;
+ os << ", ";
+ os << "led_state: " << cs.led_state.state();
+ os << ", ";
+ os << "button_state: " << cs.button_state;
+ os << " }";
+
+ return os;
+}
diff --git a/libs/surfaces/us2400/types.h b/libs/surfaces/us2400/types.h
new file mode 100644
index 0000000000..67f8b02544
--- /dev/null
+++ b/libs/surfaces/us2400/types.h
@@ -0,0 +1,115 @@
+/*
+ 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_types_h
+#define mackie_types_h
+
+#include <iostream>
+
+namespace ArdourSurface {
+namespace US2400 {
+
+enum surface_type_t {
+ st_mcu,
+ st_ext,
+ st_joy,
+ st_knb,
+};
+
+/**
+ This started off as an enum, but it got really annoying
+ typing ? on : off
+*/
+class LedState
+{
+public:
+ enum state_t { none, off, flashing, on };
+ LedState() : _state (none) {}
+ LedState (bool yn): _state (yn ? on : off) {}
+ LedState (state_t state): _state (state) {}
+
+ LedState& operator= (state_t s) { _state = s; return *this; }
+
+ bool operator == (const LedState & other) const
+ {
+ return state() == other.state();
+ }
+
+ bool operator != (const LedState & other) const
+ {
+ return state() != other.state();
+ }
+
+ state_t state() const { return _state; }
+
+private:
+ state_t _state;
+};
+
+extern LedState on;
+extern LedState off;
+extern LedState flashing;
+extern LedState none;
+
+enum ButtonState { neither = -1, release = 0, press = 1 };
+
+/**
+ Contains the state for a control, with some convenience
+ constructors
+*/
+struct ControlState
+{
+ ControlState(): pos(0.0), sign(0), delta(0.0), ticks(0), led_state(off), button_state(neither) {}
+
+ ControlState (LedState ls): pos(0.0), delta(0.0), led_state(ls), button_state(neither) {}
+
+ // Note that this sets both pos and delta to the flt value
+ ControlState (LedState ls, float flt): pos(flt), delta(flt), ticks(0), led_state(ls), button_state(neither) {}
+ ControlState (float flt): pos(flt), delta(flt), ticks(0), led_state(none), button_state(neither) {}
+ ControlState (float flt, unsigned int tcks): pos(flt), delta(flt), ticks(tcks), led_state(none), button_state(neither) {}
+ ControlState (ButtonState bs): pos(0.0), delta(0.0), ticks(0), led_state(none), button_state(bs) {}
+
+ /// For faders. Between 0 and 1.
+ float pos;
+
+ /// For pots. Sign. Either -1 or 1;
+ int sign;
+
+ /// For pots. Signed value of total movement. Between 0 and 1
+ float delta;
+
+ /// For pots. Unsigned number of ticks. Usually between 1 and 16.
+ unsigned int ticks;
+
+ LedState led_state;
+ ButtonState button_state;
+};
+
+std::ostream & operator << (std::ostream &, const ControlState &);
+
+class Control;
+class Fader;
+class Button;
+class Strip;
+class Group;
+class Pot;
+class Led;
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/us2400_control_exception.h b/libs/surfaces/us2400/us2400_control_exception.h
new file mode 100644
index 0000000000..ba86d7d847
--- /dev/null
+++ b/libs/surfaces/us2400/us2400_control_exception.h
@@ -0,0 +1,48 @@
+/*
+ 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 us2400_control_exception_h
+#define us2400_control_exception_h
+
+#include <stdexcept>
+
+namespace ArdourSurface {
+namespace US2400 {
+
+class MackieControlException : public std::exception
+{
+public:
+ MackieControlException( const std::string & msg )
+ : _msg( msg )
+ {
+ }
+
+ virtual ~MackieControlException() throw () {}
+
+ const char * what() const throw ()
+ {
+ return _msg.c_str();
+ }
+
+private:
+ std::string _msg;
+};
+
+}
+}
+
+#endif
diff --git a/libs/surfaces/us2400/us2400_control_protocol.cc b/libs/surfaces/us2400/us2400_control_protocol.cc
new file mode 100644
index 0000000000..82292f4214
--- /dev/null
+++ b/libs/surfaces/us2400/us2400_control_protocol.cc
@@ -0,0 +1,1941 @@
+/*
+ Copyright (C) 2006,2007 John Anderson
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2017 Ben Loftis
+
+ 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 <fcntl.h>
+#include <iostream>
+#include <algorithm>
+#include <cmath>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+
+#include <inttypes.h>
+#include <float.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include <boost/shared_array.hpp>
+#include <glibmm/miscutils.h>
+
+#include "midi++/types.h"
+#include "midi++/port.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/error.h"
+#include "pbd/memento_command.h"
+#include "pbd/convert.h"
+
+#include "ardour/audio_track.h"
+#include "ardour/automation_control.h"
+#include "ardour/async_midi_port.h"
+#include "ardour/dB.h"
+#include "ardour/debug.h"
+#include "ardour/location.h"
+#include "ardour/meter.h"
+#include "ardour/midi_track.h"
+#include "ardour/panner.h"
+#include "ardour/panner_shell.h"
+#include "ardour/profile.h"
+#include "ardour/record_enable_control.h"
+#include "ardour/route.h"
+#include "ardour/route_group.h"
+#include "ardour/session.h"
+#include "ardour/tempo.h"
+#include "ardour/track.h"
+#include "ardour/types.h"
+#include "ardour/audioengine.h"
+#include "ardour/vca_manager.h"
+
+#include "us2400_control_protocol.h"
+
+#include "midi_byte_array.h"
+#include "us2400_control_exception.h"
+#include "device_profile.h"
+#include "surface_port.h"
+#include "surface.h"
+#include "strip.h"
+#include "control_group.h"
+#include "meter.h"
+#include "button.h"
+#include "fader.h"
+#include "pot.h"
+
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+using namespace Glib;
+using namespace ArdourSurface;
+using namespace US2400;
+
+#include "pbd/i18n.h"
+
+#include "pbd/abstract_ui.cc" // instantiate template
+
+const int US2400Protocol::MODIFIER_OPTION = 0x1;
+const int US2400Protocol::MODIFIER_CONTROL = 0x2;
+const int US2400Protocol::MODIFIER_SHIFT = 0x4;
+const int US2400Protocol::MODIFIER_CMDALT = 0x8;
+const int US2400Protocol::MODIFIER_ZOOM = 0x10;
+const int US2400Protocol::MODIFIER_SCRUB = 0x20;
+const int US2400Protocol::MODIFIER_MARKER = 0x40;
+const int US2400Protocol::MODIFIER_DROP = 0x80; //US2400 Drop as a modifier for In/out
+const int US2400Protocol::MAIN_MODIFIER_MASK = (US2400Protocol::MODIFIER_OPTION|
+ US2400Protocol::MODIFIER_CONTROL|
+ US2400Protocol::MODIFIER_SHIFT|
+ US2400Protocol::MODIFIER_CMDALT);
+
+US2400Protocol* US2400Protocol::_instance = 0;
+
+bool US2400Protocol::probe()
+{
+ return true;
+}
+
+US2400Protocol::US2400Protocol (Session& session)
+ : ControlProtocol (session, X_("Tascam US-2400"))
+ , AbstractUI<US2400ControlUIRequest> (name())
+ , _current_initial_bank (0)
+ , _frame_last (0)
+ , _timecode_type (ARDOUR::AnyTime::BBT)
+ , _gui (0)
+ , _scrub_mode (false)
+ , _view_mode (Mixer)
+ , _subview_mode (None)
+ , _current_selected_track (-1)
+ , _modifier_state (0)
+ , _metering_active (true)
+ , _initialized (false)
+ , configuration_state (0)
+ , state_version (0)
+ , marker_modifier_consumed_by_button (false)
+ , nudge_modifier_consumed_by_button (false)
+{
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::US2400Protocol\n");
+
+// DeviceInfo::reload_device_info ();
+ DeviceProfile::reload_device_profiles ();
+
+ for (int i = 0; i < 9; i++) {
+ _last_bank[i] = 0;
+ }
+
+ PresentationInfo::Change.connect (gui_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_presentation_info_changed, this, _1), this);
+
+ _instance = this;
+
+ build_button_map ();
+}
+
+US2400Protocol::~US2400Protocol()
+{
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol init\n");
+
+ for (Surfaces::const_iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ (*si)->reset ();
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol drop_connections ()\n");
+ drop_connections ();
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol tear_down_gui ()\n");
+ tear_down_gui ();
+
+ delete configuration_state;
+
+ /* stop event loop */
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol BaseUI::quit ()\n");
+ BaseUI::quit ();
+
+ try {
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol close()\n");
+ close();
+ }
+ catch (exception & e) {
+ cout << "~US2400Protocol caught " << e.what() << endl;
+ }
+ catch (...) {
+ cout << "~US2400Protocol caught unknown" << endl;
+ }
+
+ _instance = 0;
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::~US2400Protocol done\n");
+}
+
+void
+US2400Protocol::thread_init ()
+{
+ pthread_set_name (event_loop_name().c_str());
+
+ PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+ ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+ set_thread_priority ();
+}
+
+// go to the previous track.
+void
+US2400Protocol::prev_track()
+{
+ if (_current_initial_bank >= 1) {
+ switch_banks (_current_initial_bank - 1);
+ }
+}
+
+// go to the next track.
+void
+US2400Protocol::next_track()
+{
+ Sorted sorted = get_sorted_stripables();
+ if (_current_initial_bank + n_strips() < sorted.size()) {
+ switch_banks (_current_initial_bank + 1);
+ }
+}
+
+bool
+US2400Protocol::stripable_is_locked_to_strip (boost::shared_ptr<Stripable> r) const
+{
+ for (Surfaces::const_iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ if ((*si)->stripable_is_locked_to_strip (r)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// predicate for sort call in get_sorted_stripables
+struct StripableByPresentationOrder
+{
+ bool operator () (const boost::shared_ptr<Stripable> & a, const boost::shared_ptr<Stripable> & b) const
+ {
+ return a->presentation_info().order() < b->presentation_info().order();
+ }
+
+ bool operator () (const Stripable & a, const Stripable & b) const
+ {
+ return a.presentation_info().order() < b.presentation_info().order();
+ }
+
+ bool operator () (const Stripable * a, const Stripable * b) const
+ {
+ return a->presentation_info().order() < b->presentation_info().order();
+ }
+};
+
+US2400Protocol::Sorted
+US2400Protocol::get_sorted_stripables()
+{
+ Sorted sorted;
+
+ // fetch all stripables
+ StripableList stripables;
+
+ session->get_stripables (stripables);
+
+ // sort in presentation order, and exclude master, control and hidden stripables
+ // and any stripables that are already set.
+
+ for (StripableList::iterator it = stripables.begin(); it != stripables.end(); ++it) {
+
+ boost::shared_ptr<Stripable> s = *it;
+
+ if (s->presentation_info().special()) {
+ continue;
+ }
+
+ /* don't include locked routes */
+
+ if (stripable_is_locked_to_strip (s)) {
+ continue;
+ }
+
+ switch (_view_mode) {
+ case Mixer:
+#ifdef MIXBUS
+ if (!s->presentation_info().hidden() && !s->mixbus()) {
+#else
+ if (!s->presentation_info().hidden()) {
+#endif
+ sorted.push_back (s);
+ }
+ break;
+ case Busses:
+#ifdef MIXBUS
+ if (s->mixbus()) {
+ sorted.push_back (s);
+ }
+ break
+#else
+ if (!is_track(s) && !s->presentation_info().hidden()) {
+ sorted.push_back (s);
+ }
+ break;
+#endif
+ }
+ }
+
+ sort (sorted.begin(), sorted.end(), StripableByPresentationOrder());
+ return sorted;
+}
+
+void
+US2400Protocol::refresh_current_bank()
+{
+ switch_banks (_current_initial_bank, true);
+}
+
+uint32_t
+US2400Protocol::n_strips (bool with_locked_strips) const
+{
+ uint32_t strip_count = 0;
+
+ for (Surfaces::const_iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ strip_count += (*si)->n_strips (with_locked_strips);
+ }
+
+ return strip_count;
+}
+
+int
+US2400Protocol::switch_banks (uint32_t initial, bool force)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("switch banking to start at %1 force ? %2 current = %3\n", initial, force, _current_initial_bank));
+
+ if (initial == _current_initial_bank && !force) {
+ /* everything is as it should be */
+ return 0;
+ }
+
+ Sorted sorted = get_sorted_stripables();
+ uint32_t strip_cnt = n_strips (false); // do not include locked strips
+ // in this count
+
+ if (initial >= sorted.size() && !force) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("bank target %1 exceeds route range %2\n",
+ _current_initial_bank, sorted.size()));
+ /* too high, we can't get there */
+ return -1;
+ }
+
+ if (sorted.size() <= strip_cnt && _current_initial_bank == 0 && !force) {
+ /* no banking - not enough stripables to fill all strips and we're
+ * not at the first one.
+ */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("less routes (%1) than strips (%2) and we're at the end already (%3)\n",
+ sorted.size(), strip_cnt, _current_initial_bank));
+ return -1;
+ }
+
+ _current_initial_bank = initial;
+ _current_selected_track = -1;
+
+ // Map current bank of stripables onto each surface(+strip)
+
+ if (_current_initial_bank < sorted.size()) {
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("switch to %1, %2, available stripables %3 on %4 surfaces\n",
+ _current_initial_bank, strip_cnt, sorted.size(),
+ surfaces.size()));
+
+ // link stripables to strips
+
+ Sorted::iterator r = sorted.begin() + _current_initial_bank;
+
+ for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ vector<boost::shared_ptr<Stripable> > stripables;
+ uint32_t added = 0;
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface has %1 unlocked strips\n", (*si)->n_strips (false)));
+
+ for (; r != sorted.end() && added < (*si)->n_strips (false); ++r, ++added) {
+ stripables.push_back (*r);
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("give surface #%1 %2 stripables\n", (*si)->number(), stripables.size()));
+
+ (*si)->map_stripables (stripables);
+ }
+
+ } else {
+ /* all strips need to be reset */
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("clear all strips, bank target %1 is outside route range %2\n",
+ _current_initial_bank, sorted.size()));
+ for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ vector<boost::shared_ptr<Stripable> > stripables;
+ /* pass in an empty stripables list, so that all strips will be reset */
+ (*si)->map_stripables (stripables);
+ }
+ return -1;
+ }
+
+ /* current bank has not been saved */
+ session->set_dirty();
+
+ return 0;
+}
+
+int
+US2400Protocol::set_active (bool yn)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose("US2400Protocol::set_active init with yn: '%1'\n", yn));
+
+ if (yn == active()) {
+ return 0;
+ }
+
+ if (yn) {
+
+ /* start event loop */
+
+ BaseUI::run ();
+
+ connect_session_signals ();
+
+ if (!_device_info.name().empty()) {
+ set_device (_device_info.name(), true);
+ }
+
+ /* set up periodic task for timecode display and metering and automation
+ */
+
+ Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds
+ periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &US2400Protocol::periodic));
+ periodic_timeout->attach (main_loop()->get_context());
+
+ /* periodic task used to update strip displays */
+
+ Glib::RefPtr<Glib::TimeoutSource> redisplay_timeout = Glib::TimeoutSource::create (10); // milliseconds
+ redisplay_connection = redisplay_timeout->connect (sigc::mem_fun (*this, &US2400Protocol::redisplay));
+ redisplay_timeout->attach (main_loop()->get_context());
+
+ } else {
+
+ BaseUI::quit ();
+ close ();
+
+ }
+
+ ControlProtocol::set_active (yn);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose("US2400Protocol::set_active done with yn: '%1'\n", yn));
+
+ return 0;
+}
+
+bool
+US2400Protocol::hui_heartbeat ()
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->hui_heartbeat ();
+ }
+
+ return true;
+}
+
+bool
+US2400Protocol::periodic ()
+{
+ if (!active()) {
+ return false;
+ }
+
+ if (!_initialized) {
+ initialize();
+ }
+
+ ARDOUR::microseconds_t now_usecs = ARDOUR::get_microseconds ();
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->periodic (now_usecs);
+ }
+ }
+
+ return true;
+}
+
+bool
+US2400Protocol::redisplay ()
+{
+ return true;
+}
+
+void
+US2400Protocol::update_timecode_beats_led()
+{
+}
+
+void
+US2400Protocol::update_global_button (int id, LedState ls)
+{
+ boost::shared_ptr<Surface> surface;
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+
+ if (!_device_info.has_global_controls()) {
+ return;
+ }
+ // surface needs to be master surface
+ surface = _master_surface;
+ }
+
+ map<int,Control*>::iterator x = surface->controls_by_device_independent_id.find (id);
+ if (x != surface->controls_by_device_independent_id.end()) {
+ Button * button = dynamic_cast<Button*> (x->second);
+ surface->write (button->set_state (ls));
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Button %1 not found\n", id));
+ }
+}
+
+void
+US2400Protocol::update_global_led (int id, LedState ls)
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+
+ if (!_device_info.has_global_controls()) {
+ return;
+ }
+ boost::shared_ptr<Surface> surface = _master_surface;
+
+ map<int,Control*>::iterator x = surface->controls_by_device_independent_id.find (id);
+
+ if (x != surface->controls_by_device_independent_id.end()) {
+ Led * led = dynamic_cast<Led*> (x->second);
+ DEBUG_TRACE (DEBUG::US2400, "Writing LedState\n");
+ surface->write (led->set_state (ls));
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Led %1 not found\n", id));
+ }
+}
+
+void
+US2400Protocol::device_ready ()
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("device ready init (active=%1)\n", active()));
+ update_surfaces ();
+
+ update_global_button (Button::Send, off);
+ update_global_button (Button::Scrub, off);
+ update_global_button (Button::Pan, on);
+ update_global_button (Button::Flip, off);
+
+ set_subview_mode (US2400Protocol::None, first_selected_stripable());
+}
+
+// send messages to surface to set controls to correct values
+void
+US2400Protocol::update_surfaces()
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("US2400Protocol::update_surfaces() init (active=%1)\n", active()));
+ if (!active()) {
+ return;
+ }
+
+ // do the initial bank switch to connect signals
+ // _current_initial_bank is initialised by set_state
+ (void) switch_banks (_current_initial_bank, true);
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::update_surfaces() finished\n");
+}
+
+void
+US2400Protocol::initialize()
+{
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+
+ if (!_master_surface->active ()) {
+ return;
+ }
+
+ }
+
+ // update global buttons and displays
+
+ notify_transport_state_changed();
+
+ _initialized = true;
+}
+
+void
+US2400Protocol::connect_session_signals()
+{
+ // receive routes added
+ session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_routes_added, this, _1), this);
+ // receive VCAs added
+ session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_vca_added, this, _1), this);
+
+ // receive record state toggled
+ session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_record_state_changed, this), this);
+ // receive transport state changed
+ session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_transport_state_changed, this), this);
+ session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_loop_state_changed, this), this);
+ // receive punch-in and punch-out
+ Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_parameter_changed, this, _1), this);
+ session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_parameter_changed, this, _1), this);
+ // receive rude solo changed
+ session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::notify_solo_active_changed, this, _1), this);
+
+ // make sure remote id changed signals reach here
+ // see also notify_stripable_added
+ Sorted sorted = get_sorted_stripables();
+}
+
+void
+US2400Protocol::set_profile (const string& profile_name)
+{
+ map<string,DeviceProfile>::iterator d = DeviceProfile::device_profiles.find (profile_name);
+
+ if (d == DeviceProfile::device_profiles.end()) {
+ _device_profile = DeviceProfile (profile_name);
+ return;
+ }
+
+ _device_profile = d->second;
+}
+
+int
+US2400Protocol::set_device_info (const string& device_name)
+{
+return 0;
+}
+
+int
+US2400Protocol::set_device (const string& device_name, bool force)
+{
+ if (device_name == device_info().name() && !force) {
+ /* already using that device, nothing to do */
+ return 0;
+ }
+ /* get state from the current setup, and make sure it is stored in
+ the configuration_states node so that if we switch back to this device,
+ we will have its state available.
+ */
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ if (!surfaces.empty()) {
+ update_configuration_state ();
+ }
+ }
+
+ if (set_device_info (device_name)) {
+ return -1;
+ }
+
+ clear_surfaces ();
+ port_connection.disconnect ();
+ hui_connection.disconnect ();
+
+ if (_device_info.device_type() == DeviceInfo::HUI) {
+ Glib::RefPtr<Glib::TimeoutSource> hui_timeout = Glib::TimeoutSource::create (1000); // milliseconds
+ hui_connection = hui_timeout->connect (sigc::mem_fun (*this, &US2400Protocol::hui_heartbeat));
+ hui_timeout->attach (main_loop()->get_context());
+ }
+
+ /* notice that the handler for this will execute in our event
+ loop, not in the thread where the
+ PortConnectedOrDisconnected signal is emitted.
+ */
+ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&US2400Protocol::connection_handler, this, _1, _2, _3, _4, _5), this);
+
+ if (create_surfaces ()) {
+ return -1;
+ }
+
+ DeviceChanged ();
+
+ return 0;
+}
+
+int
+US2400Protocol::create_surfaces ()
+{
+ string device_name;
+ surface_type_t stype = st_mcu; // type not yet determined
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Create %1 surfaces for %2\n", 1 + _device_info.extenders(), _device_info.name()));
+
+ _input_bundle.reset (new ARDOUR::Bundle (_("US2400 Control In"), true));
+ _output_bundle.reset (new ARDOUR::Bundle (_("US2400 Control Out"), false));
+
+ for (uint32_t n = 0; n < 1 + _device_info.extenders(); ++n) {
+ bool is_master = false;
+
+ if (n == _device_info.master_position()) {
+ is_master = true;
+ }
+
+ device_name = string_compose (X_("US-2400 Control %1"), n+1);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Port Name for surface %1 is %2\n", n, device_name));
+
+ boost::shared_ptr<Surface> surface;
+
+ if (n ==0) {
+ stype = st_mcu;
+ } else if (n ==1) {
+ stype = st_ext; //ch8..16
+ } else if (n ==2) {
+ stype = st_ext; //ch17..24
+ } else if (n ==3) {
+ stype = st_joy; //joystick
+ } else if (n ==4) {
+ stype = st_knb; //chan knobs ???
+ }
+ try {
+ surface.reset (new Surface (*this, device_name, n, stype));
+ } catch (...) {
+ return -1;
+ }
+
+ if (is_master) {
+ _master_surface = surface;
+ }
+
+ if (configuration_state) {
+ XMLNode* this_device = 0;
+ XMLNodeList const& devices = configuration_state->children();
+ for (XMLNodeList::const_iterator d = devices.begin(); d != devices.end(); ++d) {
+ XMLProperty const * prop = (*d)->property (X_("name"));
+ if (prop && prop->value() == _device_info.name()) {
+ this_device = *d;
+ break;
+ }
+ }
+ if (this_device) {
+ XMLNode* snode = this_device->child (X_("Surfaces"));
+ if (snode) {
+ surface->set_state (*snode, state_version);
+ }
+ }
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ surfaces.push_back (surface);
+ }
+
+ if ( n <=3 ) { //ports 5&6 are not really used by us2400
+
+ _input_bundle->add_channel (
+ surface->port().input_port().name(),
+ ARDOUR::DataType::MIDI,
+ session->engine().make_port_name_non_relative (surface->port().input_port().name())
+ );
+
+ _output_bundle->add_channel (
+ surface->port().output_port().name(),
+ ARDOUR::DataType::MIDI,
+ session->engine().make_port_name_non_relative (surface->port().output_port().name())
+ );
+ }
+
+ MIDI::Port& input_port (surface->port().input_port());
+ AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*> (&input_port);
+
+ if (asp) {
+
+ /* async MIDI port */
+
+ asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &US2400Protocol::midi_input_handler), &input_port));
+ asp->xthread().attach (main_loop()->get_context());
+
+ }
+ }
+
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->port().reconnect ();
+ }
+
+ session->BundleAddedOrRemoved ();
+
+ assert (_master_surface);
+
+ return 0;
+}
+
+void
+US2400Protocol::close()
+{
+ port_connection.disconnect ();
+ session_connections.drop_connections ();
+ stripable_connections.drop_connections ();
+ periodic_connection.disconnect ();
+
+ clear_surfaces();
+}
+
+/** Ensure that the configuration_state XML node contains an up-to-date
+ * copy of the state node the current device. If configuration_state already
+ * contains a state node for the device, it will deleted and replaced.
+ */
+void
+US2400Protocol::update_configuration_state ()
+{
+ /* CALLER MUST HOLD SURFACES LOCK */
+
+ if (!configuration_state) {
+ configuration_state = new XMLNode (X_("Configurations"));
+ }
+
+ XMLNode* devnode = new XMLNode (X_("Configuration"));
+ devnode->set_property (X_("name"), _device_info.name());
+
+ configuration_state->remove_nodes_and_delete (X_("name"), _device_info.name());
+ configuration_state->add_child_nocopy (*devnode);
+
+ XMLNode* snode = new XMLNode (X_("Surfaces"));
+
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ snode->add_child_nocopy ((*s)->get_state());
+ }
+
+ devnode->add_child_nocopy (*snode);
+}
+
+XMLNode&
+US2400Protocol::get_state()
+{
+ XMLNode& node (ControlProtocol::get_state());
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::get_state init\n");
+
+ // add current bank
+ node.set_property (X_("bank"), _current_initial_bank);
+
+ node.set_property (X_("device-profile"), _device_profile.name());
+ node.set_property (X_("device-name"), _device_info.name());
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ update_configuration_state ();
+ }
+
+ /* force a copy of the _surfaces_state node, because we want to retain ownership */
+ node.add_child_copy (*configuration_state);
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::get_state done\n");
+
+ return node;
+}
+
+bool
+US2400Protocol::profile_exists (string const & name) const
+{
+ return DeviceProfile::device_profiles.find (name) != DeviceProfile::device_profiles.end();
+}
+
+int
+US2400Protocol::set_state (const XMLNode & node, int version)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("US2400Protocol::set_state: active %1\n", active()));
+
+ if (ControlProtocol::set_state (node, version)) {
+ return -1;
+ }
+
+ uint32_t bank = 0;
+ // fetch current bank
+ node.get_property (X_("bank"), bank);
+
+ std::string device_name;
+ if (node.get_property (X_("device-name"), device_name)) {
+ set_device_info (device_name);
+ }
+
+ std::string device_profile_name;
+ if (node.get_property (X_("device-profile"), device_profile_name)) {
+ if (device_profile_name.empty()) {
+ string default_profile_name;
+
+ /* start by looking for a user-edited profile for the current device name */
+
+ default_profile_name = DeviceProfile::name_when_edited (_device_info.name());
+
+ if (!profile_exists (default_profile_name)) {
+
+ /* no user-edited profile for this device name, so try the user-edited default profile */
+
+ default_profile_name = DeviceProfile::name_when_edited (DeviceProfile::default_profile_name);
+
+ if (!profile_exists (default_profile_name)) {
+
+ /* no user-edited version, so just try the device name */
+
+ default_profile_name = _device_info.name();
+
+ if (!profile_exists (default_profile_name)) {
+
+ /* no generic device specific profile, just try the fixed default */
+ default_profile_name = DeviceProfile::default_profile_name;
+ }
+ }
+ }
+
+ set_profile (default_profile_name);
+
+ } else {
+ if (profile_exists (device_profile_name)) {
+ set_profile (device_profile_name);
+ } else {
+ set_profile (DeviceProfile::default_profile_name);
+ }
+ }
+ }
+
+ XMLNode* dnode = node.child (X_("Configurations"));
+
+ delete configuration_state;
+ configuration_state = 0;
+
+ if (dnode) {
+ configuration_state = new XMLNode (*dnode);
+ state_version = version;
+ }
+
+ (void) switch_banks (bank, true);
+
+ DEBUG_TRACE (DEBUG::US2400, "US2400Protocol::set_state done\n");
+
+ return 0;
+}
+
+///////////////////////////////////////////
+// Session signals
+///////////////////////////////////////////
+
+void US2400Protocol::notify_parameter_changed (std::string const & p)
+{
+}
+
+void
+US2400Protocol::notify_stripable_removed ()
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->master_monitor_may_have_changed ();
+ }
+}
+
+void
+US2400Protocol::notify_vca_added (ARDOUR::VCAList& vl)
+{
+ refresh_current_bank ();
+}
+
+// RouteList is the set of Routes that have just been added
+void
+US2400Protocol::notify_routes_added (ARDOUR::RouteList & rl)
+{
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+ }
+
+ /* special case: single route, and it is the monitor or master out */
+
+ if (rl.size() == 1 && (rl.front()->is_monitor() || rl.front()->is_master())) {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->master_monitor_may_have_changed ();
+ }
+ }
+
+ // currently assigned banks are less than the full set of
+ // strips, so activate the new strip now.
+
+ refresh_current_bank();
+
+ // otherwise route added, but current bank needs no updating
+}
+
+void
+US2400Protocol::notify_solo_active_changed (bool active)
+{
+ boost::shared_ptr<Surface> surface;
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+
+ surface = _master_surface;
+ }
+
+ map<int,Control*>::iterator x = surface->controls_by_device_independent_id.find (Led::RudeSolo);
+ if (x != surface->controls_by_device_independent_id.end()) {
+ Led* rude_solo = dynamic_cast<Led*> (x->second);
+ if (rude_solo) {
+ surface->write (rude_solo->set_state (active ? flashing : off));
+ }
+ }
+}
+
+void
+US2400Protocol::notify_presentation_info_changed (PBD::PropertyChange const & what_changed)
+{
+ PBD::PropertyChange order_or_hidden;
+
+ order_or_hidden.add (Properties::hidden);
+ order_or_hidden.add (Properties::order);
+
+ if (!what_changed.contains (order_or_hidden)) {
+ return;
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (surfaces.empty()) {
+ return;
+ }
+ }
+
+ refresh_current_bank();
+}
+
+///////////////////////////////////////////
+// Transport signals
+///////////////////////////////////////////
+
+void
+US2400Protocol::notify_loop_state_changed()
+{
+}
+
+void
+US2400Protocol::notify_transport_state_changed()
+{
+ if (!_device_info.has_global_controls()) {
+ return;
+ }
+
+ // switch various play and stop buttons on / off
+ update_global_button (Button::Play, session->transport_speed() == 1.0);
+ update_global_button (Button::Stop, session->transport_stopped ());
+ update_global_button (Button::Rewind, session->transport_speed() < 0.0);
+ update_global_button (Button::Ffwd, session->transport_speed() > 1.0);
+
+ // sometimes a return to start leaves time code at old time
+ _timecode_last = string (10, ' ');
+
+ notify_metering_state_changed ();
+}
+
+void
+US2400Protocol::notify_metering_state_changed()
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->notify_metering_state_changed ();
+ }
+}
+
+void
+US2400Protocol::notify_record_state_changed ()
+{
+ if (!_device_info.has_global_controls()) {
+ return;
+ }
+
+ boost::shared_ptr<Surface> surface;
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ if (surfaces.empty()) {
+ return;
+ }
+ surface = _master_surface;
+ }
+
+ /* rec is a tristate */
+
+ map<int,Control*>::iterator x = surface->controls_by_device_independent_id.find (Button::Record);
+ if (x != surface->controls_by_device_independent_id.end()) {
+ Button * rec = dynamic_cast<Button*> (x->second);
+ if (rec) {
+ LedState ls;
+
+ switch (session->record_status()) {
+ case Session::Disabled:
+ DEBUG_TRACE (DEBUG::US2400, "record state changed to disabled, LED off\n");
+ ls = off;
+ break;
+ case Session::Recording:
+ DEBUG_TRACE (DEBUG::US2400, "record state changed to recording, LED on\n");
+ ls = on;
+ break;
+ case Session::Enabled:
+ DEBUG_TRACE (DEBUG::US2400, "record state changed to enabled, LED flashing\n");
+ ls = flashing;
+ break;
+ }
+
+ surface->write (rec->set_state (ls));
+ }
+ }
+}
+
+list<boost::shared_ptr<ARDOUR::Bundle> >
+US2400Protocol::bundles ()
+{
+ list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+ if (_input_bundle) {
+ b.push_back (_input_bundle);
+ b.push_back (_output_bundle);
+ }
+
+ return b;
+}
+
+void
+US2400Protocol::do_request (US2400ControlUIRequest* req)
+{
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("doing request type %1\n", req->type));
+ if (req->type == CallSlot) {
+
+ call_slot (MISSING_INVALIDATOR, req->the_slot);
+
+ } else if (req->type == Quit) {
+
+ stop ();
+ }
+}
+
+int
+US2400Protocol::stop ()
+{
+ BaseUI::quit ();
+
+ return 0;
+}
+
+void
+US2400Protocol::update_led (Surface& surface, Button& button, US2400::LedState ls)
+{
+ if (ls != none) {
+ surface.port().write (button.set_state (ls));
+ }
+}
+
+void
+US2400Protocol::build_button_map ()
+{
+ /* this maps our device-independent button codes to the methods that handle them.
+ */
+
+#define DEFINE_BUTTON_HANDLER(b,p,r) button_map.insert (pair<Button::ID,ButtonHandlers> ((b), ButtonHandlers ((p),(r))));
+
+ DEFINE_BUTTON_HANDLER (Button::Solo, &US2400Protocol::clearsolo_press, &US2400Protocol::clearsolo_release); // ClearSolo button == Option+Solo lands here.
+
+ DEFINE_BUTTON_HANDLER (Button::Send, &US2400Protocol::send_press, &US2400Protocol::send_release);
+ DEFINE_BUTTON_HANDLER (Button::Pan, &US2400Protocol::pan_press, &US2400Protocol::pan_release);
+ DEFINE_BUTTON_HANDLER (Button::Left, &US2400Protocol::left_press, &US2400Protocol::left_release);
+ DEFINE_BUTTON_HANDLER (Button::Right, &US2400Protocol::right_press, &US2400Protocol::right_release);
+ DEFINE_BUTTON_HANDLER (Button::Flip, &US2400Protocol::flip_press, &US2400Protocol::flip_release);
+// DEFINE_BUTTON_HANDLER (Button::F1, &US2400Protocol::F1_press, &US2400Protocol::F1_release);
+// DEFINE_BUTTON_HANDLER (Button::F2, &US2400Protocol::F2_press, &US2400Protocol::F2_release);
+// DEFINE_BUTTON_HANDLER (Button::F3, &US2400Protocol::F3_press, &US2400Protocol::F3_release);
+// DEFINE_BUTTON_HANDLER (Button::F4, &US2400Protocol::F4_press, &US2400Protocol::F4_release);
+// DEFINE_BUTTON_HANDLER (Button::F5, &US2400Protocol::F5_press, &US2400Protocol::F5_release);
+// DEFINE_BUTTON_HANDLER (Button::F6, &US2400Protocol::F6_press, &US2400Protocol::F6_release);
+ DEFINE_BUTTON_HANDLER (Button::Shift, &US2400Protocol::shift_press, &US2400Protocol::shift_release);
+ DEFINE_BUTTON_HANDLER (Button::Option, &US2400Protocol::option_press, &US2400Protocol::option_release);
+ DEFINE_BUTTON_HANDLER (Button::Drop, &US2400Protocol::drop_press, &US2400Protocol::drop_release);
+ DEFINE_BUTTON_HANDLER (Button::Rewind, &US2400Protocol::rewind_press, &US2400Protocol::rewind_release);
+ DEFINE_BUTTON_HANDLER (Button::Ffwd, &US2400Protocol::ffwd_press, &US2400Protocol::ffwd_release);
+ DEFINE_BUTTON_HANDLER (Button::Stop, &US2400Protocol::stop_press, &US2400Protocol::stop_release);
+ DEFINE_BUTTON_HANDLER (Button::Play, &US2400Protocol::play_press, &US2400Protocol::play_release);
+ DEFINE_BUTTON_HANDLER (Button::Record, &US2400Protocol::record_press, &US2400Protocol::record_release);
+ DEFINE_BUTTON_HANDLER (Button::Scrub, &US2400Protocol::scrub_press, &US2400Protocol::scrub_release);
+ DEFINE_BUTTON_HANDLER (Button::MasterFaderTouch, &US2400Protocol::master_fader_touch_press, &US2400Protocol::master_fader_touch_release);
+}
+
+void
+US2400Protocol::handle_button_event (Surface& surface, Button& button, ButtonState bs)
+{
+ Button::ID button_id = button.bid();
+
+ if (bs != press && bs != release) {
+ update_led (surface, button, none);
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Handling %1 for button %2 (%3)\n", (bs == press ? "press" : "release"), button.id(),
+ Button::id_to_name (button.bid())));
+
+ /* check profile first */
+
+ string action = _device_profile.get_button_action (button.bid(), _modifier_state);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("device profile returned [%1] for that button\n", action));
+
+ if (!action.empty()) {
+
+ if (action.find ('/') != string::npos) { /* good chance that this is really an action */
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("Looked up action for button %1 with modifier %2, got [%3]\n",
+ button.bid(), _modifier_state, action));
+
+ /* if there is a bound action for this button, and this is a press event,
+ carry out the action. If its a release event, do nothing since we
+ don't bind to them at all but don't want any other handling to
+ occur either.
+ */
+ if (bs == press) {
+ update_led (surface, button, on);
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("executing action %1\n", action));
+ access_action (action);
+ } else {
+ update_led (surface, button, off);
+ }
+ return;
+
+ } else {
+
+ /* "action" is more likely to be a button name. We use this to
+ * allow remapping buttons to different (builtin) functionality
+ * associated with an existing button. This is similar to the
+ * way that (for example) Nuendo moves the "Shift" function to
+ * the "Enter" key of the MCU Pro.
+ */
+
+ int bid = Button::name_to_id (action);
+
+ if (bid < 0) {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("apparent button name %1 not found\n", action));
+ return;
+ }
+
+ button_id = (Button::ID) bid;
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("handling button %1 as if it was %2 (%3)\n", Button::id_to_name (button.bid()), button_id, Button::id_to_name (button_id)));
+ }
+ }
+
+ /* Now that we have the correct (maybe remapped) button ID, do these
+ * checks on it.
+ */
+
+ /* lookup using the device-INDEPENDENT button ID */
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("now looking up button ID %1\n", button_id));
+
+ ButtonMap::iterator b = button_map.find (button_id);
+
+ if (b != button_map.end()) {
+
+ ButtonHandlers& bh (b->second);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("button found in map, now invoking %1\n", (bs == press ? "press" : "release")));
+
+ switch (bs) {
+ case press:
+ surface.write (button.set_state ((this->*(bh.press)) (button)));
+ break;
+ case release:
+ surface.write (button.set_state ((this->*(bh.release)) (button)));
+ break;
+ default:
+ break;
+ }
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("no button handlers for button ID %1 (device ID %2)\n",
+ button.bid(), button.id()));
+ error << string_compose ("no button handlers for button ID %1 (device ID %2)\n",
+ button.bid(), button.id()) << endmsg;
+ }
+}
+
+bool
+US2400Protocol::midi_input_handler (IOCondition ioc, MIDI::Port* port)
+{
+ if (ioc & ~IO_IN) {
+ DEBUG_TRACE (DEBUG::US2400, "MIDI port closed\n");
+ return false;
+ }
+
+ if (ioc & IO_IN) {
+
+ // DEBUG_TRACE (DEBUG::US2400, string_compose ("something happend on %1\n", port->name()));
+
+ /* Devices using regular JACK MIDI ports will need to have
+ the x-thread FIFO drained to avoid burning endless CPU.
+ */
+
+ AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port);
+ if (asp) {
+ asp->clear ();
+ }
+
+ // DEBUG_TRACE (DEBUG::US2400, string_compose ("data available on %1\n", port->name()));
+ samplepos_t now = session->engine().sample_time();
+ port->parse (now);
+ }
+
+ return true;
+}
+
+void
+US2400Protocol::clear_ports ()
+{
+ if (_input_bundle) {
+ _input_bundle->remove_channels ();
+ _output_bundle->remove_channels ();
+ }
+}
+
+void
+US2400Protocol::notify_subview_stripable_deleted ()
+{
+ /* return to global/mixer view */
+ _subview_stripable.reset ();
+ set_view_mode (Mixer);
+}
+
+bool
+US2400Protocol::subview_mode_would_be_ok (SubViewMode mode, boost::shared_ptr<Stripable> r)
+{
+ switch (mode) {
+ case None:
+ return true;
+ break;
+
+ case TrackView:
+ if (r) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+US2400Protocol::redisplay_subview_mode ()
+{
+ Surfaces copy; /* can't hold surfaces lock while calling Strip::subview_mode_changed */
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ copy = surfaces;
+ }
+
+ for (Surfaces::iterator s = copy.begin(); s != copy.end(); ++s) {
+ (*s)->subview_mode_changed ();
+ }
+
+ /* don't call this again from a timeout */
+ return false;
+}
+
+int
+US2400Protocol::set_subview_mode (SubViewMode sm, boost::shared_ptr<Stripable> r)
+{
+ if (!subview_mode_would_be_ok (sm, r)) {
+
+ DEBUG_TRACE (DEBUG::US2400, "subview mode not OK\n");
+
+ if (r) {
+
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ if (!surfaces.empty()) {
+
+ string msg;
+
+ switch (sm) {
+ case TrackView:
+ msg = _("no track view possible");
+ default:
+ break;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ boost::shared_ptr<Stripable> old_stripable = _subview_stripable;
+
+ _subview_mode = sm;
+ _subview_stripable = r;
+
+ if (_subview_stripable != old_stripable) {
+ subview_stripable_connections.drop_connections ();
+
+ /* Catch the current subview stripable going away */
+ if (_subview_stripable) {
+ _subview_stripable->DropReferences.connect (subview_stripable_connections, MISSING_INVALIDATOR,
+ boost::bind (&US2400Protocol::notify_subview_stripable_deleted, this),
+ this);
+ }
+ }
+
+ redisplay_subview_mode ();
+
+ /* turn buttons related to vpot mode on or off as required */
+
+ switch (_subview_mode) {
+ case US2400Protocol::None:
+ update_global_button (Button::Send, off);
+ update_global_button (Button::Pan, on);
+ break;
+ case US2400Protocol::TrackView:
+ update_global_button (Button::Send, off);
+ update_global_button (Button::Pan, off);
+ break;
+ }
+
+ return 0;
+}
+
+void
+US2400Protocol::set_view_mode (ViewMode m)
+{
+ ViewMode old_view_mode = _view_mode;
+
+ _view_mode = m;
+ _last_bank[old_view_mode] = _current_initial_bank;
+
+ if (switch_banks(_last_bank[m], true)) {
+ _view_mode = old_view_mode;
+ return;
+ }
+
+ /* leave subview mode, whatever it was */
+ set_subview_mode (None, boost::shared_ptr<Stripable>());
+}
+
+void
+US2400Protocol::display_view_mode ()
+{
+
+}
+
+void
+US2400Protocol::set_master_on_surface_strip (uint32_t surface, uint32_t strip_number)
+{
+ force_special_stripable_to_strip (session->master_out(), surface, strip_number);
+}
+
+void
+US2400Protocol::set_monitor_on_surface_strip (uint32_t surface, uint32_t strip_number)
+{
+ force_special_stripable_to_strip (session->monitor_out(), surface, strip_number);
+}
+
+void
+US2400Protocol::force_special_stripable_to_strip (boost::shared_ptr<Stripable> r, uint32_t surface, uint32_t strip_number)
+{
+ if (!r) {
+ return;
+ }
+
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ if ((*s)->number() == surface) {
+ Strip* strip = (*s)->nth_strip (strip_number);
+ if (strip) {
+ strip->set_stripable (session->master_out());
+ strip->lock_controls ();
+ }
+ }
+ }
+}
+
+void
+US2400Protocol::check_fader_automation_state ()
+{
+}
+
+void
+US2400Protocol::update_fader_automation_state ()
+{
+}
+
+samplepos_t
+US2400Protocol::transport_frame() const
+{
+ return session->transport_sample();
+}
+
+void
+US2400Protocol::add_down_select_button (int surface, int strip)
+{
+ _down_select_buttons.insert ((surface<<8)|(strip&0xf));
+}
+
+void
+US2400Protocol::remove_down_select_button (int surface, int strip)
+{
+ DownButtonList::iterator x = find (_down_select_buttons.begin(), _down_select_buttons.end(), (uint32_t) (surface<<8)|(strip&0xf));
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("removing surface %1 strip %2 from down select buttons\n", surface, strip));
+ if (x != _down_select_buttons.end()) {
+ _down_select_buttons.erase (x);
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface %1 strip %2 not found in down select buttons\n",
+ surface, strip));
+ }
+}
+
+void
+US2400Protocol::select_range (uint32_t pressed)
+{
+ StripableList stripables;
+
+ pull_stripable_range (_down_select_buttons, stripables, pressed);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("select range: found %1 stripables, first = %2\n", stripables.size(),
+ (stripables.empty() ? "null" : stripables.front()->name())));
+
+ if (stripables.empty()) {
+ return;
+ }
+
+ if (stripables.size() == 1 && ControlProtocol::last_selected().size() == 1 && stripables.front()->is_selected()) {
+ /* cancel selection for one and only selected stripable */
+ ToggleStripableSelection (stripables.front());
+ } else {
+ for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
+
+ if (main_modifier_state() == MODIFIER_SHIFT) {
+ ToggleStripableSelection (*s);
+ } else {
+ if (s == stripables.begin()) {
+ SetStripableSelection (*s);
+ } else {
+ AddStripableToSelection (*s);
+ }
+ }
+ }
+ }
+}
+
+void
+US2400Protocol::add_down_button (AutomationType a, int surface, int strip)
+{
+ DownButtonMap::iterator m = _down_buttons.find (a);
+
+ if (m == _down_buttons.end()) {
+ _down_buttons[a] = DownButtonList();
+ }
+
+ _down_buttons[a].insert ((surface<<8)|(strip&0xf));
+}
+
+void
+US2400Protocol::remove_down_button (AutomationType a, int surface, int strip)
+{
+ DownButtonMap::iterator m = _down_buttons.find (a);
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("removing surface %1 strip %2 from down buttons for %3\n", surface, strip, (int) a));
+
+ if (m == _down_buttons.end()) {
+ return;
+ }
+
+ DownButtonList& l (m->second);
+ DownButtonList::iterator x = find (l.begin(), l.end(), (surface<<8)|(strip&0xf));
+
+ if (x != l.end()) {
+ l.erase (x);
+ } else {
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("surface %1 strip %2 not found in down buttons for %3\n",
+ surface, strip, (int) a));
+ }
+}
+
+US2400Protocol::ControlList
+US2400Protocol::down_controls (AutomationType p, uint32_t pressed)
+{
+ ControlList controls;
+ StripableList stripables;
+
+ DownButtonMap::iterator m = _down_buttons.find (p);
+
+ if (m == _down_buttons.end()) {
+ return controls;
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("looking for down buttons for %1, got %2\n",
+ p, m->second.size()));
+
+ pull_stripable_range (m->second, stripables, pressed);
+
+ switch (p) {
+ case GainAutomation:
+ for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
+ controls.push_back ((*s)->gain_control());
+ }
+ break;
+ case SoloAutomation:
+ for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
+ controls.push_back ((*s)->solo_control());
+ }
+ break;
+ case MuteAutomation:
+ for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
+ controls.push_back ((*s)->mute_control());
+ }
+ break;
+ case RecEnableAutomation:
+ for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
+ boost::shared_ptr<AutomationControl> ac = (*s)->rec_enable_control();
+ if (ac) {
+ controls.push_back (ac);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return controls;
+
+}
+
+struct ButtonRangeSorter {
+ bool operator() (const uint32_t& a, const uint32_t& b) {
+ return (a>>8) < (b>>8) // a.surface < b.surface
+ ||
+ ((a>>8) == (b>>8) && (a&0xf) < (b&0xf)); // a.surface == b.surface && a.strip < b.strip
+ }
+};
+
+void
+US2400Protocol::pull_stripable_range (DownButtonList& down, StripableList& selected, uint32_t pressed)
+{
+ ButtonRangeSorter cmp;
+
+ if (down.empty()) {
+ return;
+ }
+
+ list<uint32_t> ldown;
+ ldown.insert (ldown.end(), down.begin(), down.end());
+ ldown.sort (cmp);
+
+ uint32_t first = ldown.front();
+ uint32_t last = ldown.back ();
+
+ uint32_t first_surface = first>>8;
+ uint32_t first_strip = first&0xf;
+
+ uint32_t last_surface = last>>8;
+ uint32_t last_strip = last&0xf;
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("PRR %5 in list %1.%2 - %3.%4\n", first_surface, first_strip, last_surface, last_strip,
+ down.size()));
+
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+
+ if ((*s)->number() >= first_surface && (*s)->number() <= last_surface) {
+
+ uint32_t fs;
+ uint32_t ls;
+
+ if ((*s)->number() == first_surface) {
+ fs = first_strip;
+ } else {
+ fs = 0;
+ }
+
+ if ((*s)->number() == last_surface) {
+ ls = last_strip;
+ ls += 1;
+ } else {
+ ls = (*s)->n_strips ();
+ }
+
+ DEBUG_TRACE (DEBUG::US2400, string_compose ("adding strips for surface %1 (%2 .. %3)\n",
+ (*s)->number(), fs, ls));
+
+ for (uint32_t n = fs; n < ls; ++n) {
+ Strip* strip = (*s)->nth_strip (n);
+ boost::shared_ptr<Stripable> r = strip->stripable();
+ if (r) {
+ if (global_index_locked (*strip) == pressed) {
+ selected.push_front (r);
+ } else {
+ selected.push_back (r);
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void
+US2400Protocol::clear_surfaces ()
+{
+ clear_ports ();
+
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ _master_surface.reset ();
+ surfaces.clear ();
+ }
+}
+
+void
+US2400Protocol::set_touch_sensitivity (int sensitivity)
+{
+ sensitivity = min (9, sensitivity);
+ sensitivity = max (0, sensitivity);
+
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->set_touch_sensitivity (sensitivity);
+ }
+}
+
+void
+US2400Protocol::recalibrate_faders ()
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->recalibrate_faders ();
+ }
+}
+
+void
+US2400Protocol::toggle_backlight ()
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ (*s)->toggle_backlight ();
+ }
+}
+
+boost::shared_ptr<Surface>
+US2400Protocol::get_surface_by_raw_pointer (void* ptr) const
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ if ((*s).get() == (Surface*) ptr) {
+ return *s;
+ }
+ }
+
+ return boost::shared_ptr<Surface> ();
+}
+
+boost::shared_ptr<Surface>
+US2400Protocol::nth_surface (uint32_t n) const
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s, --n) {
+ if (n == 0) {
+ return *s;
+ }
+ }
+
+ return boost::shared_ptr<Surface> ();
+}
+
+void
+US2400Protocol::connection_handler (boost::weak_ptr<ARDOUR::Port> wp1, std::string name1, boost::weak_ptr<ARDOUR::Port> wp2, std::string name2, bool yn)
+{
+ Surfaces scopy;
+ {
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ scopy = surfaces;
+ }
+
+ for (Surfaces::const_iterator s = scopy.begin(); s != scopy.end(); ++s) {
+ if ((*s)->connection_handler (wp1, name1, wp2, name2, yn)) {
+ ConnectionChange (*s);
+ break;
+ }
+ }
+}
+
+bool
+US2400Protocol::is_track (boost::shared_ptr<Stripable> r) const
+{
+ return boost::dynamic_pointer_cast<Track>(r) != 0;
+}
+
+bool
+US2400Protocol::is_audio_track (boost::shared_ptr<Stripable> r) const
+{
+ return boost::dynamic_pointer_cast<AudioTrack>(r) != 0;
+}
+
+bool
+US2400Protocol::is_midi_track (boost::shared_ptr<Stripable> r) const
+{
+ return boost::dynamic_pointer_cast<MidiTrack>(r) != 0;
+}
+
+bool
+US2400Protocol::is_mapped (boost::shared_ptr<Stripable> r) const
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ if ((*s)->stripable_is_mapped (r)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+US2400Protocol::stripable_selection_changed ()
+{
+ //this function is called after the stripable selection is "stable", so this is the place to check surface selection state
+ for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) {
+ (*si)->update_strip_selection ();
+ }
+
+ boost::shared_ptr<Stripable> s = first_selected_stripable ();
+ if (s) {
+ check_fader_automation_state ();
+
+ /* It is possible that first_selected_route() may return null if we
+ * are no longer displaying/mapping that route. In that case,
+ * we will exit subview mode. If first_selected_route() is
+ * null, and subview mode is not None, then the first call to
+ * set_subview_mode() will fail, and we will reset to None.
+ */
+
+ if (set_subview_mode (TrackView, s)) {
+ set_subview_mode (None, boost::shared_ptr<Stripable>());
+ }
+
+ } else
+ set_subview_mode (None, boost::shared_ptr<Stripable>());
+}
+
+boost::shared_ptr<Stripable>
+US2400Protocol::first_selected_stripable () const
+{
+ boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
+
+ if (s) {
+ /* check it is on one of our surfaces */
+
+ if (is_mapped (s)) {
+ return s;
+ }
+
+ /* stripable is not mapped. thus, the currently selected stripable is
+ * not on the surfaces, and so from our perspective, there is
+ * no currently selected stripable.
+ */
+
+ s.reset ();
+ }
+
+ return s; /* may be null */
+}
+
+boost::shared_ptr<Stripable>
+US2400Protocol::subview_stripable () const
+{
+ return _subview_stripable;
+}
+
+uint32_t
+US2400Protocol::global_index (Strip& strip)
+{
+ Glib::Threads::Mutex::Lock lm (surfaces_lock);
+ return global_index_locked (strip);
+}
+
+uint32_t
+US2400Protocol::global_index_locked (Strip& strip)
+{
+ uint32_t global = 0;
+
+ for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
+ if ((*s).get() == strip.surface()) {
+ return global + strip.index();
+ }
+ global += (*s)->n_strips ();
+ }
+
+ return global;
+}
+
+void*
+US2400Protocol::request_factory (uint32_t num_requests)
+{
+ /* AbstractUI<T>::request_buffer_factory() is a template method only
+ instantiated in this source module. To provide something visible for
+ use in the interface/descriptor, we have this static method that is
+ template-free.
+ */
+ return request_buffer_factory (num_requests);
+}
+
+void
+US2400Protocol::set_automation_state (AutoState as)
+{
+ boost::shared_ptr<Stripable> r = first_selected_stripable ();
+
+ if (!r) {
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> ac = r->gain_control();
+
+ if (!ac) {
+ return;
+ }
+
+ ac->set_automation_state (as);
+}
diff --git a/libs/surfaces/us2400/us2400_control_protocol.h b/libs/surfaces/us2400/us2400_control_protocol.h
new file mode 100644
index 0000000000..861ada9e16
--- /dev/null
+++ b/libs/surfaces/us2400/us2400_control_protocol.h
@@ -0,0 +1,480 @@
+/*
+ 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 ardour_us2400_control_protocol_h
+#define ardour_us2400_control_protocol_h
+
+#include <vector>
+#include <map>
+#include <list>
+#include <set>
+
+#include <sys/time.h>
+#include <pthread.h>
+#include <boost/smart_ptr.hpp>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+#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 "jog_wheel.h"
+#include "timer.h"
+#include "device_info.h"
+#include "device_profile.h"
+
+namespace ARDOUR {
+ class AutomationControl;
+ class Port;
+}
+
+namespace MIDI {
+ class Port;
+}
+
+namespace ArdourSurface {
+
+namespace US2400 {
+ class Surface;
+ class Control;
+ class SurfacePort;
+ class Button;
+ class Strip;
+}
+
+struct US2400ControlUIRequest : public BaseUI::BaseRequestObject {
+public:
+ US2400ControlUIRequest () {}
+ ~US2400ControlUIRequest () {}
+};
+
+class US2400Protocol
+ : public ARDOUR::ControlProtocol
+ , public AbstractUI<US2400ControlUIRequest>
+{
+ public:
+ static const int MODIFIER_OPTION;
+ static const int MODIFIER_CONTROL;
+ static const int MODIFIER_SHIFT;
+ static const int MODIFIER_CMDALT;
+ static const int MODIFIER_ZOOM;
+ static const int MODIFIER_SCRUB;
+ static const int MODIFIER_MARKER;
+ static const int MODIFIER_DROP; //US2400 replaces MODIFIER_NUDGE which is unused
+ static const int MAIN_MODIFIER_MASK;
+
+ enum ViewMode {
+ Mixer,
+ Busses,
+ };
+
+ enum SubViewMode {
+ None,
+ TrackView,
+ };
+
+ US2400Protocol(ARDOUR::Session &);
+ virtual ~US2400Protocol();
+
+ static US2400Protocol* instance() { return _instance; }
+
+ const US2400::DeviceInfo& device_info() const { return _device_info; }
+ US2400::DeviceProfile& device_profile() { return _device_profile; }
+
+ PBD::Signal0<void> DeviceChanged;
+ PBD::Signal1<void,boost::shared_ptr<US2400::Surface> > ConnectionChange;
+
+ void device_ready ();
+
+ int set_active (bool yn);
+ int set_device (const std::string&, bool force);
+ void set_profile (const std::string&);
+
+ ViewMode view_mode () const { return _view_mode; }
+ SubViewMode subview_mode () const { return _subview_mode; }
+ static bool subview_mode_would_be_ok (SubViewMode, boost::shared_ptr<ARDOUR::Stripable>);
+ boost::shared_ptr<ARDOUR::Stripable> subview_stripable() const;
+ bool zoom_mode () const { return modifier_state() & MODIFIER_ZOOM; }
+ bool metering_active () const { return _metering_active; }
+
+ bool is_track (boost::shared_ptr<ARDOUR::Stripable>) const;
+ bool is_audio_track (boost::shared_ptr<ARDOUR::Stripable>) const;
+ bool is_midi_track (boost::shared_ptr<ARDOUR::Stripable>) const;
+ bool is_mapped (boost::shared_ptr<ARDOUR::Stripable>) const;
+ boost::shared_ptr<ARDOUR::Stripable> first_selected_stripable () const;
+
+ void check_fader_automation_state ();
+ void update_fader_automation_state ();
+ void set_automation_state (ARDOUR::AutoState);
+
+ void set_view_mode (ViewMode);
+ int set_subview_mode (SubViewMode, boost::shared_ptr<ARDOUR::Stripable>);
+ void display_view_mode ();
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ /* Note: because Mackie control is inherently a duplex protocol,
+ we do not implement get/set_feedback() since this aspect of
+ support for the protocol is not optional.
+ */
+
+ static bool probe();
+ static void* request_factory (uint32_t);
+
+ mutable Glib::Threads::Mutex surfaces_lock;
+ typedef std::list<boost::shared_ptr<US2400::Surface> > Surfaces;
+ Surfaces surfaces;
+
+ boost::shared_ptr<US2400::Surface> get_surface_by_raw_pointer (void*) const;
+ boost::shared_ptr<US2400::Surface> nth_surface (uint32_t) const;
+
+ uint32_t global_index (US2400::Strip&);
+ uint32_t global_index_locked (US2400::Strip&);
+
+ std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
+
+ void set_master_on_surface_strip (uint32_t surface, uint32_t strip);
+ void set_monitor_on_surface_strip (uint32_t surface, uint32_t strip);
+
+ uint32_t n_strips (bool with_locked_strips = true) const;
+
+ bool has_editor () const { return true; }
+ void* get_gui () const;
+ void tear_down_gui ();
+
+ void handle_button_event (US2400::Surface&, US2400::Button& button, US2400::ButtonState);
+
+ void notify_subview_stripable_deleted ();
+ void notify_stripable_removed ();
+ void notify_routes_added (ARDOUR::RouteList &);
+ void notify_vca_added (ARDOUR::VCAList &);
+
+ void notify_presentation_info_changed(PBD::PropertyChange const &);
+
+ void recalibrate_faders ();
+ void toggle_backlight ();
+ void set_touch_sensitivity (int);
+
+ /// rebuild the current bank. Called on route or vca added/removed and
+ /// presentation info changed.
+ void refresh_current_bank();
+
+ // button-related signals
+ void notify_record_state_changed();
+ void notify_transport_state_changed();
+ void notify_loop_state_changed();
+ void notify_metering_state_changed();
+ // mainly to pick up punch-in and punch-out
+ void notify_parameter_changed(std::string const &);
+ void notify_solo_active_changed(bool);
+
+ /// Turn timecode on and beats off, or vice versa, depending
+ /// on state of _timecode_type
+ void update_timecode_beats_led();
+
+ /// this is called to generate the midi to send in response to a button press.
+ void update_led(US2400::Surface&, US2400::Button & button, US2400::LedState);
+
+ void update_global_button (int id, US2400::LedState);
+ void update_global_led (int id, US2400::LedState);
+
+ ARDOUR::Session & get_session() { return *session; }
+ samplepos_t transport_frame() const;
+
+ int modifier_state() const { return _modifier_state; }
+ int main_modifier_state() const { return _modifier_state & MAIN_MODIFIER_MASK; }
+
+ typedef std::list<boost::shared_ptr<ARDOUR::AutomationControl> > ControlList;
+
+ void add_down_button (ARDOUR::AutomationType, int surface, int strip);
+ void remove_down_button (ARDOUR::AutomationType, int surface, int strip);
+ ControlList down_controls (ARDOUR::AutomationType, uint32_t pressed);
+
+ void add_down_select_button (int surface, int strip);
+ void remove_down_select_button (int surface, int strip);
+ void select_range (uint32_t pressed);
+
+ protected:
+ // shut down the surface
+ void close();
+
+ // This sets up the notifications and sets the
+ // controls to the correct values
+ void update_surfaces();
+
+ // connects global (not strip) signals from the Session to here
+ // so the surface can be notified of changes from the other UIs.
+ void connect_session_signals();
+
+ // set all controls to their zero position
+ void zero_all();
+
+ /**
+ Fetch the set of Stripables to be considered for control by the
+ surface. Excluding master, hidden and control routes, and inactive routes
+ */
+ typedef std::vector<boost::shared_ptr<ARDOUR::Stripable> > Sorted;
+ Sorted get_sorted_stripables();
+
+ // bank switching
+ int switch_banks (uint32_t first_remote_id, bool force = false);
+ void prev_track ();
+ void next_track ();
+
+ void do_request (US2400ControlUIRequest*);
+ int stop ();
+
+ void thread_init ();
+
+ bool stripable_is_locked_to_strip (boost::shared_ptr<ARDOUR::Stripable>) const;
+
+ private:
+
+ struct ButtonHandlers {
+ US2400::LedState (US2400Protocol::*press) (US2400::Button&);
+ US2400::LedState (US2400Protocol::*release) (US2400::Button&);
+
+ ButtonHandlers (US2400::LedState (US2400Protocol::*p) (US2400::Button&),
+ US2400::LedState (US2400Protocol::*r) (US2400::Button&))
+ : press (p)
+ , release (r) {}
+ };
+
+ typedef std::map<US2400::Button::ID,ButtonHandlers> ButtonMap;
+
+ static US2400Protocol* _instance;
+
+ bool profile_exists (std::string const&) const;
+
+ US2400::DeviceInfo _device_info;
+ US2400::DeviceProfile _device_profile;
+ sigc::connection periodic_connection;
+ sigc::connection redisplay_connection;
+ sigc::connection hui_connection;
+ uint32_t _current_initial_bank;
+ PBD::ScopedConnectionList audio_engine_connections;
+ PBD::ScopedConnectionList session_connections;
+ PBD::ScopedConnectionList stripable_connections;
+ PBD::ScopedConnectionList subview_stripable_connections;
+ PBD::ScopedConnectionList gui_connections;
+ // timer for two quick marker left presses
+ US2400::Timer _frm_left_last;
+ // last written timecode string
+ std::string _timecode_last;
+ samplepos_t _frame_last;
+ // Which timecode are we displaying? BBT or Timecode
+ ARDOUR::AnyTime::Type _timecode_type;
+ // Bundle to represent our input ports
+ boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
+ // Bundle to represent our output ports
+ boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
+ void* _gui;
+ bool _scrub_mode;
+ ViewMode _view_mode;
+ SubViewMode _subview_mode;
+ boost::shared_ptr<ARDOUR::Stripable> _subview_stripable;
+ int _current_selected_track;
+ int _modifier_state;
+ ButtonMap button_map;
+ bool _metering_active;
+ bool _initialized;
+ XMLNode* configuration_state;
+ int state_version;
+ int _last_bank[9];
+ bool marker_modifier_consumed_by_button;
+ bool nudge_modifier_consumed_by_button;
+
+ boost::shared_ptr<ArdourSurface::US2400::Surface> _master_surface;
+
+ int create_surfaces ();
+ bool periodic();
+ bool redisplay();
+ bool redisplay_subview_mode ();
+ bool hui_heartbeat ();
+ void build_gui ();
+ bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port);
+ void clear_ports ();
+ void clear_surfaces ();
+ void force_special_stripable_to_strip (boost::shared_ptr<ARDOUR::Stripable> r, uint32_t surface, uint32_t strip_number);
+ void build_button_map ();
+ void stripable_selection_changed ();
+ void initialize ();
+ int set_device_info (const std::string& device_name);
+ void update_configuration_state ();
+
+ /* MIDI port connection management */
+
+ PBD::ScopedConnection port_connection;
+ void connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool);
+
+ /* BUTTON HANDLING */
+
+ typedef std::set<uint32_t> DownButtonList;
+ typedef std::map<ARDOUR::AutomationType,DownButtonList> DownButtonMap;
+ DownButtonMap _down_buttons;
+ DownButtonList _down_select_buttons;
+
+ void pull_stripable_range (DownButtonList&, ARDOUR::StripableList&, uint32_t pressed);
+
+ /* implemented button handlers */
+ US2400::LedState stop_press(US2400::Button &);
+ US2400::LedState stop_release(US2400::Button &);
+ US2400::LedState play_press(US2400::Button &);
+ US2400::LedState play_release(US2400::Button &);
+ US2400::LedState record_press(US2400::Button &);
+ US2400::LedState record_release(US2400::Button &);
+ US2400::LedState loop_press(US2400::Button &);
+ US2400::LedState loop_release(US2400::Button &);
+ US2400::LedState rewind_press(US2400::Button & button);
+ US2400::LedState rewind_release(US2400::Button & button);
+ US2400::LedState ffwd_press(US2400::Button & button);
+ US2400::LedState ffwd_release(US2400::Button & button);
+ US2400::LedState cursor_up_press (US2400::Button &);
+ US2400::LedState cursor_up_release (US2400::Button &);
+ US2400::LedState cursor_down_press (US2400::Button &);
+ US2400::LedState cursor_down_release (US2400::Button &);
+ US2400::LedState cursor_left_press (US2400::Button &);
+ US2400::LedState cursor_left_release (US2400::Button &);
+ US2400::LedState cursor_right_press (US2400::Button &);
+ US2400::LedState cursor_right_release (US2400::Button &);
+ US2400::LedState left_press(US2400::Button &);
+ US2400::LedState left_release(US2400::Button &);
+ US2400::LedState right_press(US2400::Button &);
+ US2400::LedState right_release(US2400::Button &);
+ US2400::LedState channel_left_press(US2400::Button &);
+ US2400::LedState channel_left_release(US2400::Button &);
+ US2400::LedState channel_right_press(US2400::Button &);
+ US2400::LedState channel_right_release(US2400::Button &);
+ US2400::LedState marker_press(US2400::Button &);
+ US2400::LedState marker_release(US2400::Button &);
+ US2400::LedState save_press(US2400::Button &);
+ US2400::LedState save_release(US2400::Button &);
+ US2400::LedState timecode_beats_press(US2400::Button &);
+ US2400::LedState timecode_beats_release(US2400::Button &);
+ US2400::LedState zoom_press(US2400::Button &);
+ US2400::LedState zoom_release(US2400::Button &);
+ US2400::LedState scrub_press(US2400::Button &);
+ US2400::LedState scrub_release(US2400::Button &);
+ US2400::LedState undo_press (US2400::Button &);
+ US2400::LedState undo_release (US2400::Button &);
+ US2400::LedState shift_press (US2400::Button &);
+ US2400::LedState shift_release (US2400::Button &);
+ US2400::LedState option_press (US2400::Button &);
+ US2400::LedState option_release (US2400::Button &);
+ US2400::LedState control_press (US2400::Button &);
+ US2400::LedState control_release (US2400::Button &);
+ US2400::LedState cmd_alt_press (US2400::Button &);
+ US2400::LedState cmd_alt_release (US2400::Button &);
+
+ US2400::LedState pan_press (US2400::Button &);
+ US2400::LedState pan_release (US2400::Button &);
+ US2400::LedState plugin_press (US2400::Button &);
+ US2400::LedState plugin_release (US2400::Button &);
+ US2400::LedState eq_press (US2400::Button &);
+ US2400::LedState eq_release (US2400::Button &);
+ US2400::LedState dyn_press (US2400::Button &);
+ US2400::LedState dyn_release (US2400::Button &);
+ US2400::LedState flip_press (US2400::Button &);
+ US2400::LedState flip_release (US2400::Button &);
+ US2400::LedState name_value_press (US2400::Button &);
+ US2400::LedState name_value_release (US2400::Button &);
+// US2400::LedState F1_press (US2400::Button &);
+// US2400::LedState F1_release (US2400::Button &);
+// US2400::LedState F2_press (US2400::Button &);
+// US2400::LedState F2_release (US2400::Button &);
+// US2400::LedState F3_press (US2400::Button &);
+// US2400::LedState F3_release (US2400::Button &);
+// US2400::LedState F4_press (US2400::Button &);
+// US2400::LedState F4_release (US2400::Button &);
+// US2400::LedState F5_press (US2400::Button &);
+// US2400::LedState F5_release (US2400::Button &);
+// US2400::LedState F6_press (US2400::Button &);
+// US2400::LedState F6_release (US2400::Button &);
+// US2400::LedState F7_press (US2400::Button &);
+// US2400::LedState F7_release (US2400::Button &);
+// US2400::LedState F8_press (US2400::Button &);
+// US2400::LedState F8_release (US2400::Button &);
+ US2400::LedState touch_press (US2400::Button &);
+ US2400::LedState touch_release (US2400::Button &);
+ US2400::LedState enter_press (US2400::Button &);
+ US2400::LedState enter_release (US2400::Button &);
+ US2400::LedState cancel_press (US2400::Button &);
+ US2400::LedState cancel_release (US2400::Button &);
+ US2400::LedState user_a_press (US2400::Button &);
+ US2400::LedState user_a_release (US2400::Button &);
+ US2400::LedState user_b_press (US2400::Button &);
+ US2400::LedState user_b_release (US2400::Button &);
+ US2400::LedState fader_touch_press (US2400::Button &);
+ US2400::LedState fader_touch_release (US2400::Button &);
+ US2400::LedState master_fader_touch_press (US2400::Button &);
+ US2400::LedState master_fader_touch_release (US2400::Button &);
+
+ US2400::LedState read_press (US2400::Button&);
+ US2400::LedState read_release (US2400::Button&);
+ US2400::LedState write_press (US2400::Button&);
+ US2400::LedState write_release (US2400::Button&);
+ US2400::LedState clearsolo_press (US2400::Button&);
+ US2400::LedState clearsolo_release (US2400::Button&);
+ US2400::LedState track_press (US2400::Button&);
+ US2400::LedState track_release (US2400::Button&);
+ US2400::LedState send_press (US2400::Button&);
+ US2400::LedState send_release (US2400::Button&);
+ US2400::LedState miditracks_press (US2400::Button&);
+ US2400::LedState miditracks_release (US2400::Button&);
+ US2400::LedState inputs_press (US2400::Button&);
+ US2400::LedState inputs_release (US2400::Button&);
+ US2400::LedState audiotracks_press (US2400::Button&);
+ US2400::LedState audiotracks_release (US2400::Button&);
+ US2400::LedState audioinstruments_press (US2400::Button&);
+ US2400::LedState audioinstruments_release (US2400::Button&);
+ US2400::LedState aux_press (US2400::Button&);
+ US2400::LedState aux_release (US2400::Button&);
+ US2400::LedState busses_press (US2400::Button&);
+ US2400::LedState busses_release (US2400::Button&);
+ US2400::LedState outputs_press (US2400::Button&);
+ US2400::LedState outputs_release (US2400::Button&);
+ US2400::LedState user_press (US2400::Button&);
+ US2400::LedState user_release (US2400::Button&);
+ US2400::LedState trim_press (US2400::Button&);
+ US2400::LedState trim_release (US2400::Button&);
+ US2400::LedState latch_press (US2400::Button&);
+ US2400::LedState latch_release (US2400::Button&);
+ US2400::LedState grp_press (US2400::Button&);
+ US2400::LedState grp_release (US2400::Button&);
+ US2400::LedState nudge_press (US2400::Button&);
+ US2400::LedState nudge_release (US2400::Button&);
+ US2400::LedState drop_press (US2400::Button&);
+ US2400::LedState drop_release (US2400::Button&);
+ US2400::LedState replace_press (US2400::Button&);
+ US2400::LedState replace_release (US2400::Button&);
+ US2400::LedState click_press (US2400::Button&);
+ US2400::LedState click_release (US2400::Button&);
+ US2400::LedState view_press (US2400::Button&);
+ US2400::LedState view_release (US2400::Button&);
+
+ US2400::LedState bank_release (US2400::Button&, uint32_t bank_num);
+};
+
+} // namespace
+
+#endif // ardour_us2400_control_protocol_h
diff --git a/libs/surfaces/us2400/us2400_control_protocol_poll.cc b/libs/surfaces/us2400/us2400_control_protocol_poll.cc
new file mode 100644
index 0000000000..7a3b86a4f7
--- /dev/null
+++ b/libs/surfaces/us2400/us2400_control_protocol_poll.cc
@@ -0,0 +1,26 @@
+#include "us2400_control_protocol.h"
+
+#include "midi_byte_array.h"
+#include "surface_port.h"
+
+#include "pbd/pthread_utils.h"
+#include "pbd/error.h"
+
+#include "midi++/types.h"
+#include "midi++/port.h"
+#include "midi++/manager.h"
+#include "pbd/i18n.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace US2400;
+using namespace PBD;
+
diff --git a/libs/surfaces/us2400/wscript b/libs/surfaces/us2400/wscript
new file mode 100644
index 0000000000..7a7d7751da
--- /dev/null
+++ b/libs/surfaces/us2400/wscript
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import os
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+ autowaf.set_options(opt)
+
+def configure(conf):
+ autowaf.configure(conf)
+
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.source = '''
+ button.cc
+ controls.cc
+ device_info.cc
+ device_profile.cc
+ fader.cc
+ gui.cc
+ interface.cc
+ jog.cc
+ jog_wheel.cc
+ led.cc
+ us2400_control_protocol.cc
+ mcp_buttons.cc
+ meter.cc
+ midi_byte_array.cc
+ pot.cc
+ strip.cc
+ surface.cc
+ surface_port.cc
+ types.cc
+ '''
+ obj.export_includes = ['./us2400']
+ obj.defines = [ 'PACKAGE="ardour_us2400"' ]
+ obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+ obj.includes = [ '.' ]
+ obj.name = 'libardour_us2400'
+ obj.target = 'ardour_us2400'
+ obj.uselib = 'GTKMM XML'
+ obj.use = 'libardour libardour_cp libgtkmm2ext'
+ obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+ autowaf.shutdown()