diff options
author | Ben Loftis <ben@harrisonconsoles.com> | 2017-10-05 10:54:46 -0500 |
---|---|---|
committer | Ben Loftis <ben@harrisonconsoles.com> | 2017-10-05 10:55:45 -0500 |
commit | 2107d094541dd836b46be0ff48964fd44fbbeacf (patch) | |
tree | c952c4f730568031f6669ff25921b1b1f3839d07 /libs/surfaces/us2400 | |
parent | 32c725115d87752a01cae533070b9931867667cd (diff) |
US2400: add us2400 files to repository.
Diffstat (limited to 'libs/surfaces/us2400')
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() |