Add support for contourdesign ShuttlePRO v2 and ShuttleXpress
authorJohannes Mueller <github@johannes-mueller.org>
Sat, 18 May 2019 11:31:24 +0000 (13:31 +0200)
committerJohannes Mueller <github@johannes-mueller.org>
Sat, 18 May 2019 12:04:38 +0000 (14:04 +0200)
14 files changed:
gtk2_ardour/ardev_common.sh.in
libs/ardour/ardour/debug.h
libs/ardour/debug.cc
libs/surfaces/contourdesign/button_config_widget.cc [new file with mode: 0644]
libs/surfaces/contourdesign/button_config_widget.h [new file with mode: 0644]
libs/surfaces/contourdesign/contourdesign.cc [new file with mode: 0644]
libs/surfaces/contourdesign/contourdesign.h [new file with mode: 0644]
libs/surfaces/contourdesign/contourdesign_gui.cc [new file with mode: 0644]
libs/surfaces/contourdesign/interface.cc [new file with mode: 0644]
libs/surfaces/contourdesign/jump_distance_widget.cc [new file with mode: 0644]
libs/surfaces/contourdesign/jump_distance_widget.h [new file with mode: 0644]
libs/surfaces/contourdesign/wscript [new file with mode: 0644]
libs/surfaces/wscript
tools/udev/99-counterdesign.rules [new file with mode: 0644]

index 89dd925420f4a59d88bf5ae9d5969cee673d2d9b..96a7ab8cf5e4d68fe7af41d5e8776e8a9c090363 100644 (file)
@@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
 # can find all the components.
 #
 
-export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121:$libs/surfaces/launch_control_xl
+export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121:$libs/surfaces/launch_control_xl:$libs/surfaces/contourdesign
 export ARDOUR_PANNER_PATH=$libs/panners
 export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
 export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.
index 069840a14948077fa71b72d30e429478520f110c..ba4b97dc1ba572521fcbd856c1c08459d6590d3c 100644 (file)
@@ -69,6 +69,7 @@ namespace PBD {
                LIBARDOUR_API extern DebugBits OrderKeys;
                LIBARDOUR_API extern DebugBits Automation;
                LIBARDOUR_API extern DebugBits WiimoteControl;
+               LIBARDOUR_API extern DebugBits ContourDesignControl;
                LIBARDOUR_API extern DebugBits Ports;
                LIBARDOUR_API extern DebugBits AudioEngine;
                LIBARDOUR_API extern DebugBits Soundcloud;
index 3eced3063b70d5624152e2e6a0156ae3b33bbae8..1c23aa070385c8069ed671ea7112783673d7954a 100644 (file)
@@ -65,6 +65,7 @@ PBD::DebugBits PBD::DEBUG::TempoMap = PBD::new_debug_bit ("tempomap");
 PBD::DebugBits PBD::DEBUG::OrderKeys = PBD::new_debug_bit ("orderkeys");
 PBD::DebugBits PBD::DEBUG::Automation = PBD::new_debug_bit ("automation");
 PBD::DebugBits PBD::DEBUG::WiimoteControl = PBD::new_debug_bit ("wiimotecontrol");
+PBD::DebugBits PBD::DEBUG::ContourDesignControl = PBD::new_debug_bit ("contourdesigncontrol");
 PBD::DebugBits PBD::DEBUG::Ports = PBD::new_debug_bit ("Ports");
 PBD::DebugBits PBD::DEBUG::AudioEngine = PBD::new_debug_bit ("AudioEngine");
 PBD::DebugBits PBD::DEBUG::Soundcloud = PBD::new_debug_bit ("Soundcloud");
diff --git a/libs/surfaces/contourdesign/button_config_widget.cc b/libs/surfaces/contourdesign/button_config_widget.cc
new file mode 100644 (file)
index 0000000..17fadc7
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#include <gtkmm/label.h>
+#include <gtkmm/treemodelsort.h>
+
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/actions.h"
+
+#include "pbd/i18n.h"
+#include "pbd/strsplit.h"
+#include "pbd/signals.h"
+
+#include "button_config_widget.h"
+
+using namespace std;
+using namespace Gtk;
+using namespace ArdourSurface;
+
+class ActionModel
+{
+public:
+       static const ActionModel& instance ();
+
+       const Glib::RefPtr<TreeStore> model () const { return _available_action_model; }
+
+       const TreeModelColumn<string>& name () const { return _action_columns.name; }
+       const TreeModelColumn<string>& path () const { return _action_columns.path; }
+
+private:
+       ActionModel ();
+       struct ActionColumns : public TreeModel::ColumnRecord {
+               ActionColumns() {
+                       add (name);
+                       add (path);
+               }
+               TreeModelColumn<string> name;
+               TreeModelColumn<string> path;
+       };
+
+       const ActionColumns _action_columns;
+       Glib::RefPtr<TreeStore> _available_action_model;
+};
+
+
+ButtonConfigWidget::ButtonConfigWidget ()
+       : HBox ()
+       , _choice_jump (_("Jump: "))
+       , _choice_action (_("Other action: "))
+       , _jump_distance (JumpDistance ())
+       , _action_model (ActionModel::instance ())
+{
+       RadioButtonGroup cbg = _choice_jump.get_group ();
+       _choice_action.set_group (cbg);
+       _choice_jump.signal_toggled().connect (sigc::mem_fun (*this, &ButtonConfigWidget::update_choice));
+
+       _jump_distance.Changed.connect (sigc::mem_fun (*this, &ButtonConfigWidget::update_config));
+
+       _action_cb.set_model (_action_model.model());
+       _action_cb.signal_changed().connect (sigc::mem_fun (*this, &ButtonConfigWidget::update_config));
+       _action_cb.pack_start (_action_model.name (), true);
+
+       HBox* jump_box = manage (new HBox);
+       jump_box->pack_start (_choice_jump, false, true);
+       jump_box->pack_start (_jump_distance, false, true);
+
+       HBox* action_box = manage (new HBox);
+       action_box->pack_start (_choice_action, false, true);
+       action_box->pack_start (_action_cb, false, true);
+
+       set_spacing (25);
+       pack_start (*jump_box, false, true);
+       pack_start (*action_box, false, true);
+}
+
+bool
+ButtonConfigWidget::find_action_in_model (const TreeModel::iterator& iter, string const& action_path, TreeModel::iterator* found)
+{
+       TreeModel::Row row = *iter;
+
+       if (action_path == string(row[_action_model.path ()])) {
+               *found = iter;
+               return true;
+       }
+
+       return false;
+}
+
+void
+ButtonConfigWidget::set_current_config (boost::shared_ptr<ButtonBase> btn_cnf)
+{
+       const ButtonAction* ba = dynamic_cast<const ButtonAction*> (btn_cnf.get());
+       if (ba) {
+               set_current_action (ba->get_path ());
+               _action_cb.set_sensitive (true);
+               _jump_distance.set_sensitive (false);
+       } else {
+               const ButtonJump* bj = static_cast<const ButtonJump*> (btn_cnf.get());
+               set_jump_distance (bj->get_jump_distance());
+               _action_cb.set_sensitive (false);
+               _jump_distance.set_sensitive (true);
+       }
+}
+
+boost::shared_ptr<ButtonBase>
+ButtonConfigWidget::get_current_config (ContourDesignControlProtocol& ccp) const
+{
+       if (_choice_jump.get_active ()) {
+               return boost::shared_ptr<ButtonBase> (new ButtonJump (JumpDistance (_jump_distance.get_distance ()), ccp));
+       }
+
+       TreeModel::const_iterator row = _action_cb.get_active ();
+       string action_path = (*row)[_action_model.path ()];
+
+       return boost::shared_ptr<ButtonBase> (new ButtonAction (action_path, ccp));
+}
+
+
+void
+ButtonConfigWidget::set_current_action (std::string action_string)
+{
+       _choice_action.set_active (true);
+       _choice_jump.set_active (false);
+       if (action_string.empty()) {
+               _action_cb.set_active (0);
+               return;
+       }
+
+       TreeModel::iterator iter = _action_model.model()->children().end();
+
+       _action_model.model()->foreach_iter (sigc::bind (sigc::mem_fun (*this, &ButtonConfigWidget::find_action_in_model),  action_string, &iter));
+
+       if (iter != _action_model.model()->children().end()) {
+               _action_cb.set_active (iter);
+       } else {
+               _action_cb.set_active (0);
+       }
+}
+
+void
+ButtonConfigWidget::set_jump_distance (JumpDistance dist)
+{
+       _choice_jump.set_active (true);
+       _choice_action.set_active (false);
+       _jump_distance.set_distance (dist);
+
+       Changed (); /* emit signal */
+}
+
+void
+ButtonConfigWidget::update_choice ()
+{
+       _jump_distance.set_sensitive (_choice_jump.get_active ());
+       _action_cb.set_sensitive (_choice_action.get_active ());
+
+       Changed (); /* emit signal */
+}
+
+
+void
+ButtonConfigWidget::update_config ()
+{
+       Changed (); /* emit signal */
+}
+
+
+
+const ActionModel&
+ActionModel::instance ()
+{
+       static ActionModel am;
+       return am;
+}
+
+ActionModel::ActionModel ()
+{
+       _available_action_model = TreeStore::create (_action_columns);
+       _available_action_model->clear ();
+
+       typedef std::map<string,TreeIter> NodeMap;
+       NodeMap nodes;
+       NodeMap::iterator r;
+
+       TreeIter rowp;
+       TreeModel::Row parent;
+
+       rowp = _available_action_model->append ();
+       parent = *(rowp);
+       parent[_action_columns.name] = _("Disabled");
+
+       vector<string> paths;
+       vector<string> labels;
+       vector<string> tooltips;
+       vector<string> keys;
+       vector<Glib::RefPtr<Gtk::Action> > actions;
+
+       ActionManager::get_all_actions (paths, labels, tooltips, keys, actions);
+
+       vector<string>::iterator k;
+       vector<string>::iterator p;
+       vector<string>::iterator t;
+       vector<string>::iterator l;
+
+       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[0] == _("Main_menu") )
+                       continue;
+               if ( parts[0] == _("JACK") )
+                       continue;
+               if ( parts[0] == _("redirectmenu") )
+                       continue;
+               if ( parts[0] == _("Editor_menus") )
+                       continue;
+               if ( parts[0] == _("RegionList") )
+                       continue;
+               if ( parts[0] == _("ProcessorMenu") )
+                       continue;
+
+               if ((r = nodes.find (parts[0])) == nodes.end()) {
+                       /* top level is missing */
+
+                       TreeIter rowp;
+                       TreeModel::Row parent;
+                       rowp = _available_action_model->append();
+                       nodes[parts[0]] = rowp;
+                       parent = *(rowp);
+                       parent[_action_columns.name] = parts[0];
+
+                       row = *(_available_action_model->append (parent.children()));
+               } else {
+                       row = *(_available_action_model->append ((*r->second)->children()));
+               }
+
+               /* add this action */
+
+               if (l->empty ()) {
+                       row[_action_columns.name] = *t;
+               } else {
+                       row[_action_columns.name] = *l;
+               }
+
+               row[_action_columns.path] = *p;
+       }
+}
diff --git a/libs/surfaces/contourdesign/button_config_widget.h b/libs/surfaces/contourdesign/button_config_widget.h
new file mode 100644 (file)
index 0000000..c1f8f52
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#ifndef ardour_contourdesign_button_config_widget_h
+#define ardour_contourdesign_button_config_widget_h
+
+#include <gtkmm/box.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/treestore.h>
+
+#include "contourdesign.h"
+#include "jump_distance_widget.h"
+
+class ActionModel;
+
+namespace ArdourSurface
+{
+class ButtonConfigWidget : public Gtk::HBox
+{
+public:
+       ButtonConfigWidget ();
+       ~ButtonConfigWidget () {};
+
+       void set_current_config (boost::shared_ptr<ButtonBase> btn_cnf);
+       boost::shared_ptr<ButtonBase> get_current_config (ContourDesignControlProtocol& ccp) const;
+
+       sigc::signal<void> Changed;
+
+private:
+       void set_current_action (std::string action_string);
+       void set_jump_distance (JumpDistance dist);
+
+       Gtk::RadioButton _choice_jump;
+       Gtk::RadioButton _choice_action;
+
+       void update_choice ();
+       void update_config ();
+
+       bool find_action_in_model (const Gtk::TreeModel::iterator& iter, std::string const& action_path, Gtk::TreeModel::iterator* found);
+
+       JumpDistanceWidget _jump_distance;
+       Gtk::ComboBox _action_cb;
+
+       const ActionModel& _action_model;
+};
+}
+
+#endif /* ardour_contourdesign_button_config_widget_h */
diff --git a/libs/surfaces/contourdesign/contourdesign.cc b/libs/surfaces/contourdesign/contourdesign.cc
new file mode 100644 (file)
index 0000000..b0976f0
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#include <iostream>
+
+#include <libusb.h>
+
+#include <glibmm.h>
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "ardour/debug.h"
+#include "ardour/session.h"
+#include "ardour/tempo.h"
+#include "pbd/i18n.h"
+
+#include "contourdesign.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Glib;
+using namespace std;
+using namespace ArdourSurface;
+
+#include "pbd/abstract_ui.cc" // instantiate template
+
+static const uint16_t ContourDesign = 0x0b33;
+static const uint16_t ShuttlePRO_id = 0x0030;
+static const uint16_t ShuttleXpress_id = 0x0020;
+
+static void LIBUSB_CALL event_callback (struct libusb_transfer* transfer);
+
+
+ContourDesignControlProtocol::ContourDesignControlProtocol (Session& session)
+       : ControlProtocol (session, X_("ContourDesign"))
+       ,  AbstractUI<ContourDesignControlUIRequest> ("contourdesign")
+       , _io_source (0)
+       , _dev_handle (0)
+       , _usb_transfer (0)
+       , _supposed_to_quit (false)
+       , _device_type (None)
+       , _shuttle_was_zero (true)
+       , _was_rolling_before_shuttle (false)
+       , _test_mode (false)
+       , _keep_rolling (true)
+       , _shuttle_speeds ()
+       , _jog_distance ()
+       , _gui (0)
+{
+       libusb_init (0);
+//     libusb_set_debug(0, LIBUSB_LOG_LEVEL_WARNING);
+
+       _shuttle_speeds.push_back (0.50);
+       _shuttle_speeds.push_back (0.75);
+       _shuttle_speeds.push_back (1.0);
+       _shuttle_speeds.push_back (1.5);
+       _shuttle_speeds.push_back (2.0);
+       _shuttle_speeds.push_back (5.0);
+       _shuttle_speeds.push_back (10.0);
+
+       setup_default_button_actions ();
+       BaseUI::run();
+}
+
+ContourDesignControlProtocol::~ContourDesignControlProtocol ()
+{
+       stop ();
+       libusb_exit (0);
+       BaseUI::quit();
+       tear_down_gui ();
+}
+
+bool
+ContourDesignControlProtocol::probe ()
+{
+       return true;
+}
+
+int
+ContourDesignControlProtocol::set_active (bool yn)
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("set_active() init with yn: '%1'\n", yn));
+
+       if (yn == active()) {
+               return 0;
+       }
+
+       if (yn) {
+               start ();
+       } else {
+               stop ();
+       }
+
+       ControlProtocol::set_active (yn);
+
+       return _error;
+}
+
+XMLNode&
+ContourDesignControlProtocol::get_state ()
+{
+       XMLNode& node (ControlProtocol::get_state());
+       node.set_property (X_("keep-rolling"), _keep_rolling);
+
+       ostringstream os;
+       vector<double>::const_iterator it = _shuttle_speeds.begin ();
+       os << *(it++);
+       for (; it != _shuttle_speeds.end (); ++it) {
+               os << ' ' << *it;
+       }
+       string s = os.str ();
+       node.set_property (X_("shuttle-speeds"), s);
+
+       node.set_property (X_("jog-distance"), _jog_distance.value);
+       switch (_jog_distance.unit) {
+       case SECONDS: s = X_("seconds"); break;
+       case BARS: s = X_("bars"); break;
+       case BEATS:
+       default: s = X_("beats");
+       }
+       node.set_property (X_("jog-unit"), s);
+
+       for (unsigned int i=0; i<_button_actions.size(); ++i) {
+               XMLNode* child = new XMLNode (string_compose (X_("button-%1"), i+1));
+               node.add_child_nocopy (_button_actions[i]->get_state (*child));
+       }
+
+       return node;
+}
+
+int
+ContourDesignControlProtocol::set_state (const XMLNode& node, int version)
+{
+       if (ControlProtocol::set_state (node, version)) {
+               return -1;
+       }
+       node.get_property (X_("keep-rolling"), _keep_rolling);
+
+       string s;
+       node.get_property (X_("shuttle-speeds"), s);
+       istringstream is (s);
+       for (vector<double>::iterator it = _shuttle_speeds.begin (); it != _shuttle_speeds.end (); ++it) {
+               is >> *it;
+       }
+
+       node.get_property (X_("jog-distance"), _jog_distance.value);
+       node.get_property (X_("jog-unit"), s);
+       if (s == "seconds") {
+               _jog_distance.unit = SECONDS;
+       } else if (s == "bars") {
+               _jog_distance.unit = BARS;
+       } else {
+               _jog_distance.unit = BEATS;
+       }
+
+       XMLNode* child;
+       for (unsigned int i=0; i<_button_actions.size(); ++i) {
+               if ((child = node.child (string_compose(X_("button-%1"), i+1).c_str())) == 0) {
+                       continue;
+               }
+               string type;
+               child->get_property (X_("type"), type);
+               if (type == X_("action")) {
+                       string path ("");
+                       child->get_property (X_("path"), path);
+                       boost::shared_ptr<ButtonBase> b (new ButtonAction (path, *this));
+                       _button_actions[i] = b;
+               } else {
+                       double value;
+                       child->get_property(X_("value"), value);
+
+                       string s;
+                       child->get_property(X_("unit"), s);
+                       JumpUnit unit;
+                       if (s == X_("seconds")) {
+                               unit = SECONDS;
+                       } else if (s == X_("bars")) {
+                               unit = BARS;
+                       } else {
+                               unit = BEATS;
+                       }
+
+                       boost::shared_ptr<ButtonBase> b (new ButtonJump (JumpDistance (value, unit), *this));
+               }
+       }
+
+       return 0;
+}
+
+void
+ContourDesignControlProtocol::do_request (ContourDesignControlUIRequest* req)
+{
+       if (req->type == CallSlot) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "do_request type CallSlot\n");
+               call_slot (MISSING_INVALIDATOR, req->the_slot);
+       } else if (req->type == Quit) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "do_request type Quit\n");
+               stop ();
+       }
+}
+
+void
+ContourDesignControlProtocol::thread_init ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "thread_init()\n");
+
+       pthread_set_name (X_("contourdesign"));
+       PBD::notify_event_loops_about_thread_creation (pthread_self (), X_("contourdesign"), 2048);
+       ARDOUR::SessionEvent::create_per_thread_pool (X_("contourdesign"), 128);
+
+       set_thread_priority ();
+}
+
+bool
+ContourDesignControlProtocol::wait_for_event ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "wait_for_event\n");
+       if (!_supposed_to_quit) {
+               libusb_handle_events (0);
+       }
+
+       return true;
+}
+
+int
+get_usb_device (uint16_t vendor_id, uint16_t product_id, libusb_device** device)
+{
+       struct libusb_device **devs;
+       struct libusb_device *dev;
+       size_t i = 0;
+       int r;
+
+       *device = 0;
+
+       if (libusb_get_device_list (0, &devs) < 0) {
+               return LIBUSB_ERROR_NO_DEVICE;
+       }
+
+       while ((dev = devs[i++])) {
+               struct libusb_device_descriptor desc;
+               r = libusb_get_device_descriptor (dev, &desc);
+               if (r < 0) {
+                       goto out;
+               }
+               if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
+                       *device = dev;
+                       break;
+               }
+       }
+
+out:
+       libusb_free_device_list(devs, 1);
+       if (!dev && !r) {
+               return LIBUSB_ERROR_NO_DEVICE;
+       }
+       return r;
+}
+
+int
+ContourDesignControlProtocol::acquire_device ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "acquire_device()\n");
+
+       int err;
+
+       if (_dev_handle) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "already have a device handle\n");
+               return LIBUSB_SUCCESS;
+       }
+
+       libusb_device* dev;
+
+
+       if ((err = get_usb_device (ContourDesign, ShuttleXpress_id, &dev)) == 0) {
+               _device_type = ShuttleXpress;
+       }
+       else if ((err = get_usb_device (ContourDesign, ShuttlePRO_id, &dev)) == 0) {
+               _device_type = ShuttlePRO;
+       } else {
+               _device_type = None;
+               return err;
+       }
+
+       err = libusb_open (dev, &_dev_handle);
+       if (err < 0) {
+               return err;
+       }
+
+       libusb_set_auto_detach_kernel_driver (_dev_handle, true);
+
+       if ((err = libusb_claim_interface (_dev_handle, 0x00))) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to claim USB device\n");
+               goto usb_close;
+       }
+
+       _usb_transfer = libusb_alloc_transfer (0);
+       if (!_usb_transfer) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to alloc usb transfer\n");
+               err = LIBUSB_ERROR_NO_MEM;
+               goto usb_close;
+       }
+
+       libusb_fill_interrupt_transfer (_usb_transfer, _dev_handle, 1 | LIBUSB_ENDPOINT_IN, _buf, sizeof(_buf),
+                                      event_callback, this, 0);
+
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "callback installed\n");
+
+       if ((err = libusb_submit_transfer (_usb_transfer))) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("failed to submit tansfer: %1\n", err));
+               goto free_transfer;
+       }
+
+       return LIBUSB_SUCCESS;
+
+ free_transfer:
+       libusb_free_transfer (_usb_transfer);
+
+ usb_close:
+       libusb_close (_dev_handle);
+       _dev_handle = 0;
+       return err;
+}
+
+void
+ContourDesignControlProtocol::release_device ()
+{
+       if (!_dev_handle) {
+               return;
+       }
+
+       libusb_close (_dev_handle);
+       libusb_free_transfer (_usb_transfer);
+       libusb_release_interface (_dev_handle, 0);
+       _usb_transfer = 0;
+       _dev_handle = 0;
+}
+
+void
+ContourDesignControlProtocol::start ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "start()\n");
+
+       _supposed_to_quit = false;
+
+       _error = acquire_device();
+       if (_error) {
+               return;
+       }
+
+       if (!_dev_handle) { // can this actually happen?
+               _error = -1;
+               return;
+       }
+
+       _state.shuttle = 0;
+       _state.jog = 0;
+       _state.buttons = 0;
+
+       Glib::RefPtr<Glib::IdleSource> source = Glib::IdleSource::create ();
+       source->connect (sigc::mem_fun (*this, &ContourDesignControlProtocol::wait_for_event));
+       source->attach (_main_loop->get_context ());
+
+       _io_source = source->gobj ();
+       g_source_ref (_io_source);
+}
+
+
+void
+ContourDesignControlProtocol::stop ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "stop()\n");
+
+       _supposed_to_quit = true;
+
+       if (_io_source) {
+               g_source_destroy (_io_source);
+               g_source_unref (_io_source);
+               _io_source = 0;
+       }
+
+       if (_dev_handle) {
+               release_device ();
+       }
+}
+
+void
+ContourDesignControlProtocol::handle_event () {
+       if (_usb_transfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
+               goto resubmit;
+       }
+       if (_usb_transfer->status != LIBUSB_TRANSFER_COMPLETED) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose("libusb_transfer not completed: %1\n", _usb_transfer->status));
+               _error = LIBUSB_ERROR_NO_DEVICE;
+               return;
+       }
+
+       State new_state;
+       new_state.shuttle = _buf[0];
+       new_state.jog = _buf[1];
+       new_state.buttons = (_buf[4] << 8) + _buf[3];
+
+//     cout << "event " << (int)new_state.shuttle << " " << (int)new_state.jog << " " << (int)new_state.buttons << endl;;
+
+       for (uint8_t btn=0; btn<16; btn++) {
+                if ( (new_state.buttons & (1<<btn)) && !(_state.buttons & (1<<btn)) ) {
+                        handle_button_press (btn);
+                } else if ( !(new_state.buttons & (1<<btn)) && (_state.buttons & (1<<btn)) ) {
+                        handle_button_release (btn);
+                }
+        }
+
+       if (new_state.jog == 255 && _state.jog == 0) {
+               jog_event_backward ();
+       } else if (new_state.jog == 0 && _state.jog == 255) {
+               jog_event_forward();
+       } else if (new_state.jog < _state.jog) {
+               jog_event_backward();
+       } else if (new_state.jog > _state.jog) {
+               jog_event_forward();
+       }
+
+       if (new_state.shuttle != _state.shuttle) {
+               shuttle_event(new_state.shuttle);
+       }
+
+       _state = new_state;
+
+ resubmit:
+       if (libusb_submit_transfer (_usb_transfer)) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to resubmit usb transfer after callback\n");
+               stop ();
+       }
+}
+
+
+boost::shared_ptr<ButtonBase>
+ContourDesignControlProtocol::make_button_action (string action_string)
+{
+       return boost::shared_ptr<ButtonBase> (new ButtonAction (action_string, *this));
+}
+
+/* The buttons have the following layout
+ *
+ *          00  01  02  03
+ *        04  05  06  07  08
+ *
+ *          13   Jog   14
+ *
+ *            09     10
+ *            11     12
+ */
+
+void
+ContourDesignControlProtocol::setup_default_button_actions ()
+{
+       _button_actions.push_back (make_button_action ("MIDI/panic"));
+       _button_actions.push_back (make_button_action ("Editor/remove-last-capture"));
+       _button_actions.push_back (make_button_action ("Editor/undo"));
+       _button_actions.push_back (make_button_action ("Editor/redo"));
+       _button_actions.push_back (make_button_action ("Common/jump-backward-to-mark"));
+       _button_actions.push_back (make_button_action ("Transport/Record"));
+       _button_actions.push_back (make_button_action ("Transport/Stop"));
+       _button_actions.push_back (make_button_action ("Transport/Roll"));
+       _button_actions.push_back (make_button_action ("Common/jump-forward-to-mark"));
+       _button_actions.push_back (boost::shared_ptr<ButtonBase> (new ButtonJump (JumpDistance (-4.0, BARS), *this)));
+       _button_actions.push_back (boost::shared_ptr<ButtonBase> (new ButtonJump (JumpDistance (+4.0, BARS), *this)));
+       _button_actions.push_back (make_button_action (""));
+       _button_actions.push_back (make_button_action ("Common/add-location-from-playhead"));
+       _button_actions.push_back (make_button_action ("Transport/GotoStart"));
+       _button_actions.push_back (make_button_action ("Transport/GotoEnd"));
+}
+
+void
+ContourDesignControlProtocol::handle_button_press (unsigned short btn)
+{
+       if (_test_mode) {
+               ButtonPress (btn); /* emit signal */
+               return;
+       }
+       if (btn >= _button_actions.size ()) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl,
+                            string_compose ("ContourDesign button number out of bounds %1, max is %2\n",
+                                            btn, _button_actions.size ()));
+               return;
+       }
+
+       _button_actions[btn]->execute ();
+}
+
+void
+ContourDesignControlProtocol::handle_button_release (unsigned short btn)
+{
+       if (_test_mode) {
+               ButtonRelease (btn); /* emit signal */
+       }
+}
+
+
+void
+ContourDesignControlProtocol::prev_marker_keep_rolling ()
+{
+       samplepos_t pos = session->locations()->first_mark_before (session->transport_sample());
+
+       if (pos >= 0) {
+               session->request_locate (pos, _keep_rolling && session->transport_rolling());
+       } else {
+               session->goto_start ();
+       }
+}
+
+void
+ContourDesignControlProtocol::next_marker_keep_rolling ()
+{
+       samplepos_t pos = session->locations()->first_mark_after (session->transport_sample());
+
+       if (pos >= 0) {
+               session->request_locate (pos, _keep_rolling && session->transport_rolling());
+       } else {
+               session->goto_end();
+       }
+}
+
+void
+ContourDesignControlProtocol::jog_event_backward ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "jog event backward\n");
+       jump_backward (_jog_distance);
+}
+
+void
+ContourDesignControlProtocol::jog_event_forward ()
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, "jog event forward\n");
+       jump_forward (_jog_distance);
+}
+
+void
+ContourDesignControlProtocol::jump_forward (JumpDistance dist)
+{
+       bool kr = _keep_rolling && session->transport_rolling ();
+       switch (dist.unit) {
+       case SECONDS: jump_by_seconds (dist.value, kr); break;
+       case BEATS: jump_by_beats (dist.value, kr); break;
+       case BARS: jump_by_bars (dist.value, kr); break;
+       default: break;
+       }
+}
+
+void ContourDesignControlProtocol::jump_backward (JumpDistance dist)
+{
+       JumpDistance bw = dist;
+       bw.value = -bw.value;
+       jump_forward(bw);
+}
+
+void
+ContourDesignControlProtocol::shuttle_event(int position)
+{
+       DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("shuttle event %1\n", position));
+       if (position != 0) {
+               if (_shuttle_was_zero) {
+                       _was_rolling_before_shuttle = session->transport_rolling ();
+               }
+               double speed = position > 0 ? _shuttle_speeds[position-1] : -_shuttle_speeds[-position-1];
+               set_transport_speed (speed);
+               _shuttle_was_zero = false;
+       } else {
+               if (_keep_rolling && _was_rolling_before_shuttle) {
+                       set_transport_speed (1.0);
+               } else {
+                       transport_stop ();
+               }
+               _shuttle_was_zero = true;
+       }
+}
+
+void
+ButtonJump::execute ()
+{
+       _spc.jump_forward (_dist);
+}
+
+XMLNode&
+ButtonJump::get_state (XMLNode& node) const
+{
+       string ts (X_("jump"));
+       node.set_property (X_("type"), ts);
+       node.set_property (X_("distance"), _dist.value);
+
+       string s;
+       switch (_dist.unit) {
+       case SECONDS: s = X_("seconds"); break;
+       case BARS: s = X_("bars"); break;
+       case BEATS:
+       default: s = X_("beats");
+       }
+       node.set_property (X_("unit"), s);
+
+       return node;
+}
+
+void
+ButtonAction::execute ()
+{
+       _spc.access_action (_action_string);
+}
+
+
+XMLNode&
+ButtonAction::get_state (XMLNode& node) const
+{
+       string ts (X_("action"));
+       node.set_property (X_("type"), ts);
+       node.set_property (X_("path"), _action_string);
+
+       return node;
+}
+
+static void LIBUSB_CALL event_callback (libusb_transfer* transfer)
+{
+       ContourDesignControlProtocol* spc = static_cast<ContourDesignControlProtocol*> (transfer->user_data);
+       spc->handle_event();
+}
diff --git a/libs/surfaces/contourdesign/contourdesign.h b/libs/surfaces/contourdesign/contourdesign.h
new file mode 100644 (file)
index 0000000..730e89d
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#ifndef ardour_contourdesign_control_protocol_h
+#define ardour_contourdesign_control_protocol_h
+
+#include <string>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <glibmm/main.h>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+#include "ardour/types.h"
+#include "control_protocol/control_protocol.h"
+
+struct libusb_device_handle;
+struct libusb_transfer;
+
+class ContourDesignGUI;
+
+namespace ArdourSurface {
+
+struct ContourDesignControlUIRequest : public BaseUI::BaseRequestObject {
+public:
+       ContourDesignControlUIRequest () {}
+       ~ContourDesignControlUIRequest () {}
+};
+
+enum JumpUnit {
+       SECONDS = 0,
+       BEATS = 1,
+       BARS = 2
+};
+
+struct JumpDistance {
+       JumpDistance () : value (1.0), unit (BEATS) {}
+       JumpDistance (double v, JumpUnit u) : value (v), unit (u) {}
+       JumpDistance (const JumpDistance& o) : value (o.value), unit (o.unit) {}
+       double value;
+       JumpUnit unit;
+};
+
+class ButtonBase;
+
+
+class ContourDesignControlProtocol
+       : public ARDOUR::ControlProtocol
+       , public AbstractUI<ContourDesignControlUIRequest>
+{
+       friend ContourDesignGUI;
+public:
+       ContourDesignControlProtocol (ARDOUR::Session &);
+       virtual ~ContourDesignControlProtocol ();
+
+       enum DeviceType {
+               None = 0,
+               ShuttlePRO,
+               ShuttleXpress
+       };
+
+       DeviceType device_type() const { return _device_type; }
+
+       static bool probe ();
+
+       int set_active (bool yn);
+
+       XMLNode& get_state ();
+       int set_state (const XMLNode&, int version);
+
+       void stripable_selection_changed () {}
+
+       void handle_event ();
+
+       static const int num_shuttle_speeds = 7;
+
+       void prev_marker_keep_rolling ();
+       void next_marker_keep_rolling ();
+
+       void jump_forward (JumpDistance dist);
+       void jump_backward (JumpDistance dist);
+
+       boost::shared_ptr<ButtonBase> make_button_action (std::string action_string);
+
+private:
+       void do_request (ContourDesignControlUIRequest*);
+       void start ();
+       void stop ();
+
+       bool has_editor () const { return true; }
+       void* get_gui () const;
+       void  tear_down_gui ();
+
+       void thread_init ();
+
+       int acquire_device ();
+       void release_device ();
+
+       void handle_button_press (unsigned short btn);
+       void handle_button_release (unsigned short btn);
+
+       void jog_event_backward ();
+       void jog_event_forward ();
+
+       void shuttle_event (int position);
+
+       bool wait_for_event ();
+       GSource* _io_source;
+       libusb_device_handle* _dev_handle;
+       libusb_transfer* _usb_transfer;
+       bool _supposed_to_quit;
+
+       unsigned char _buf[5];
+
+       DeviceType _device_type;
+
+       bool _shuttle_was_zero, _was_rolling_before_shuttle;
+
+       struct State {
+               int8_t shuttle;
+               uint8_t jog;
+               uint16_t buttons;
+       };
+       State _state;
+
+       bool _test_mode;
+       PBD::Signal1<void, unsigned short> ButtonPress;
+       PBD::Signal1<void, unsigned short> ButtonRelease;
+
+       // Config stuff
+
+       bool _keep_rolling;
+       std::vector<double> _shuttle_speeds;
+       JumpDistance _jog_distance;
+
+       std::vector<boost::shared_ptr<ButtonBase> > _button_actions;
+       void setup_default_button_actions ();
+
+       mutable ContourDesignGUI* _gui;
+       void build_gui ();
+
+       int _error;
+       bool _needs_reattach;
+};
+
+
+
+class ButtonBase
+{
+public:
+       ButtonBase (ContourDesignControlProtocol& spc) : _spc (spc) {}
+       virtual ~ButtonBase () {}
+       virtual void execute () = 0;
+
+       virtual XMLNode& get_state (XMLNode& node) const = 0;
+
+protected:
+       ContourDesignControlProtocol& _spc;
+};
+
+
+class ButtonJump : public ButtonBase
+{
+public:
+       ButtonJump (JumpDistance dist, ContourDesignControlProtocol& ccp)
+               : ButtonBase (ccp)
+               , _dist (dist) {}
+       ~ButtonJump () {}
+
+       void execute ();
+       JumpDistance get_jump_distance () const { return _dist; };
+
+       XMLNode& get_state (XMLNode& node) const;
+
+private:
+       JumpDistance _dist;
+};
+
+class ButtonAction : public ButtonBase
+{
+public:
+       ButtonAction (const std::string as, ContourDesignControlProtocol& ccp)
+               : ButtonBase (ccp)
+               , _action_string (as) {}
+       ~ButtonAction () {}
+
+       void execute ();
+       std::string get_path () const { return _action_string; }
+
+       XMLNode& get_state (XMLNode& node) const;
+
+private:
+       const std::string _action_string;
+};
+
+
+
+} // namespace
+
+#endif  /* ardour_contourdesign_control_protocol_h */
diff --git a/libs/surfaces/contourdesign/contourdesign_gui.cc b/libs/surfaces/contourdesign/contourdesign_gui.cc
new file mode 100644 (file)
index 0000000..88c0523
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#include <libusb.h>
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/label.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/table.h>
+
+#include "pbd/unwind.h"
+
+#include "ardour/debug.h"
+
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "widgets/ardour_button.h"
+
+#include "pbd/i18n.h"
+
+#include "contourdesign.h"
+#include "jump_distance_widget.h"
+#include "button_config_widget.h"
+
+using namespace ArdourSurface;
+
+class ContourDesignGUI : public Gtk::VBox, public PBD::ScopedConnectionList
+{
+public:
+       ContourDesignGUI (ContourDesignControlProtocol& ccp);
+       ~ContourDesignGUI () {}
+
+private:
+       ContourDesignControlProtocol& _ccp;
+
+       ArdourWidgets::ArdourButton _test_button;
+
+       Gtk::CheckButton _keep_rolling;
+       void toggle_keep_rolling ();
+
+       std::vector<boost::shared_ptr<Gtk::Adjustment> > _shuttle_speed_adjustments;
+       void set_shuttle_speed (int index);
+
+       JumpDistanceWidget _jog_distance;
+       void update_jog_distance ();
+
+       void update_action(unsigned int index, ButtonConfigWidget* sender);
+
+       void toggle_test_mode ();
+
+       void test_button_press (unsigned short btn);
+       void test_button_release (unsigned short btn);
+
+       std::vector<boost::shared_ptr<ArdourWidgets::ArdourButton> > _btn_leds;
+
+       void init_on_show ();
+       bool reset_test_state (GdkEventAny* = 0);
+       bool update_device_state ();
+
+       Gtk::Label _device_state_lbl;
+
+       sigc::signal<void, bool> ProButtonsSensitive;
+       sigc::signal<void, bool> XpressButtonsSensitive;
+};
+
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+using namespace Glib;
+using namespace ArdourWidgets;
+
+
+ContourDesignGUI::ContourDesignGUI (ContourDesignControlProtocol& ccp)
+       : _ccp (ccp)
+       , _test_button (_("Button Test"), ArdourButton::led_default_elements)
+       , _keep_rolling (_("Keep rolling after jumps"))
+       , _jog_distance (ccp._jog_distance)
+       , _device_state_lbl ()
+{
+       Frame* dg_sample = manage (new Frame (_("Device")));
+       dg_sample->set_size_request (300, -1);
+       VBox* dg_box = manage (new VBox);
+       dg_sample->add (*dg_box);
+       dg_box->set_border_width (6);
+       dg_box->pack_start (_device_state_lbl);
+
+       _device_state_lbl.set_line_wrap (true);
+
+       Frame* sj_sample = manage (new Frame (_("Shuttle speeds and jog jump distances")));
+       Table* sj_table = manage (new Table);
+       sj_sample->set_border_width (6);
+       sj_table->set_border_width (12);
+       sj_sample->add (*sj_table);
+
+       Label* speed_label = manage (new Label (_("Transport speeds for the shuttle positions:"), ALIGN_START));
+       sj_table->attach (*speed_label, 0,1, 0,1, FILL|EXPAND, FILL|EXPAND, /* xpadding = */ 12);
+
+       HBox* speed_box = manage (new HBox);
+       for (int i=0; i != ContourDesignControlProtocol::num_shuttle_speeds; ++i) {
+               double speed = ccp._shuttle_speeds[i];
+               boost::shared_ptr<Gtk::Adjustment> adj (new Gtk::Adjustment (speed, 0.0, 100.0, 0.25));
+               _shuttle_speed_adjustments.push_back (adj);
+               SpinButton* sb = manage (new SpinButton (*adj, 0.25, 2));
+               speed_box->pack_start (*sb);
+               sb->signal_value_changed().connect (sigc::bind (sigc::mem_fun(*this, &ContourDesignGUI::set_shuttle_speed), i));
+       }
+       sj_table->attach (*speed_box, 1,2, 0,1);
+
+       Label* jog_label = manage (new Label (_("Jump distance for jog wheel:"), ALIGN_START));
+       _jog_distance.Changed.connect (sigc::mem_fun (*this, &ContourDesignGUI::update_jog_distance));
+
+       sj_table->attach (*jog_label, 0,1, 1,2, FILL|EXPAND, FILL|EXPAND, /* xpadding = */ 12);
+       sj_table->attach (_jog_distance, 1,2, 1,2);
+
+       _keep_rolling.set_tooltip_text (_("If checked Ardour keeps rolling after jog or shuttle events. If unchecked it stops."));
+       _keep_rolling.signal_toggled().connect (sigc::mem_fun (*this, &ContourDesignGUI::toggle_keep_rolling));
+       _keep_rolling.set_active (_ccp._keep_rolling);
+
+       sj_table->attach (_keep_rolling, 0,1, 2,3);
+
+
+       Frame* btn_action_sample = manage (new Frame (_("Actions or jumps for buttons")));
+       HBox* btn_action_box = manage (new HBox);
+       btn_action_sample->set_border_width (6);
+       btn_action_box->set_border_width (12);
+       btn_action_sample->add (*btn_action_box);
+
+       VBox* tbb = manage (new VBox);
+       _test_button.set_tooltip_text (_("If the button is active, all the button presses are not handled, "
+                                        "but in the corresponding line in the button table the LED will light up."));
+       _test_button.signal_clicked.connect (sigc::mem_fun (*this, &ContourDesignGUI::toggle_test_mode));
+       _test_button.set_size_request (-1, 64);
+       tbb->pack_start(_test_button, true, false);
+       btn_action_box->pack_start (*tbb, true, false, 12);
+
+
+       Table* table = manage (new Table);
+       table->set_row_spacings (6);
+       table->set_col_spacings (6);;
+
+       vector<boost::shared_ptr<ButtonBase> >::const_iterator it;
+       unsigned int btn_idx = 0;
+       for (it = _ccp._button_actions.begin(); it != _ccp._button_actions.end(); ++it) {
+               boost::shared_ptr<ArdourButton> b (new ArdourButton (string_compose (_("Setting for button %1"), btn_idx+1),
+                                                                    ArdourButton::Element(ArdourButton::Indicator|ArdourButton::Text|ArdourButton::Inactive)));
+               table->attach (*b, 0, 2, btn_idx, btn_idx+1);
+               _btn_leds.push_back (b);
+
+               ButtonConfigWidget* bcw = manage (new ButtonConfigWidget);
+
+               bcw->set_current_config (*it);
+               bcw->Changed.connect (sigc::bind (sigc::mem_fun (*this, &ContourDesignGUI::update_action), btn_idx, bcw));
+               table->attach (*bcw, 3, 5, btn_idx, btn_idx+1);
+
+               if (btn_idx > 3 && btn_idx < 9) {
+                       this->XpressButtonsSensitive.connect (sigc::mem_fun (*b, &ArdourButton::set_sensitive));
+                       this->XpressButtonsSensitive.connect (sigc::mem_fun (*bcw, &ButtonConfigWidget::set_sensitive));
+               } else {
+                       this->ProButtonsSensitive.connect (sigc::mem_fun (*b, &ArdourButton::set_sensitive));
+                       this->ProButtonsSensitive.connect (sigc::mem_fun (*bcw, &ButtonConfigWidget::set_sensitive));
+               }
+               ++btn_idx;
+       }
+
+       set_spacing (6);
+       btn_action_box->pack_start (*table, false, false);
+
+       HBox* top_box = manage (new HBox);
+       top_box->pack_start (*dg_sample);
+       top_box->pack_start (*sj_sample);
+       pack_start (*top_box);
+       pack_start (*btn_action_sample);
+
+       _ccp.ButtonPress.connect (*this, invalidator (*this), boost::bind (&ContourDesignGUI::test_button_press, this, _1), gui_context ());
+       _ccp.ButtonRelease.connect (*this, invalidator (*this), boost::bind (&ContourDesignGUI::test_button_release, this, _1), gui_context ());
+
+       signal_map().connect (sigc::mem_fun (*this, &ContourDesignGUI::init_on_show));
+       update_device_state ();
+}
+
+void
+ContourDesignGUI::toggle_keep_rolling ()
+{
+       _ccp._keep_rolling = _keep_rolling.get_active();
+}
+
+void
+ContourDesignGUI::set_shuttle_speed (int index)
+{
+       double speed = _shuttle_speed_adjustments[index]->get_value ();
+       _ccp._shuttle_speeds[index] = speed;
+}
+
+void
+ContourDesignGUI::update_jog_distance ()
+{
+       _ccp._jog_distance = _jog_distance.get_distance ();
+}
+
+void
+ContourDesignGUI::update_action (unsigned int index, ButtonConfigWidget* sender)
+{
+       if (index >= _ccp._button_actions.size()) {
+               DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("ContourDesignGUI::update_action() index out of bounds %1 / %2\n", index, _ccp._button_actions.size()));
+               return;
+       }
+       _ccp._button_actions[index] = sender->get_current_config (_ccp);
+       DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("update_action () %1\n", index));
+}
+
+void
+ContourDesignGUI::toggle_test_mode ()
+{
+       _ccp._test_mode = !_ccp._test_mode;
+       if (_ccp._test_mode) {
+               _test_button.set_active_state (ActiveState::ExplicitActive);
+       } else {
+               reset_test_state ();
+       }
+}
+
+void
+ContourDesignGUI::init_on_show ()
+{
+       Gtk::Widget* p = get_parent();
+       if (p) {
+               p->signal_delete_event().connect (sigc::mem_fun (*this, &ContourDesignGUI::reset_test_state));
+       }
+}
+
+bool
+ContourDesignGUI::reset_test_state (GdkEventAny*)
+{
+       _ccp._test_mode = false;
+       _test_button.set_active (ActiveState::Off);
+       vector<boost::shared_ptr<ArdourButton> >::const_iterator it;
+       for (it = _btn_leds.begin(); it != _btn_leds.end(); ++it) {
+               (*it)->set_active_state (ActiveState::Off);
+       }
+
+       return false;
+}
+
+void
+ContourDesignGUI::test_button_press (unsigned short btn)
+{
+       _btn_leds[btn]->set_active_state (ActiveState::ExplicitActive);
+}
+
+void
+ContourDesignGUI::test_button_release (unsigned short btn)
+{
+       _btn_leds[btn]->set_active_state (ActiveState::Off);
+}
+
+bool
+ContourDesignGUI::update_device_state ()
+{
+       switch (_ccp.device_type ()) {
+       case ContourDesignControlProtocol::ShuttlePRO:
+               _device_state_lbl.set_markup ("<span weight=\"bold\" foreground=\"green\">Found ShuttlePRO v2</span>");
+               XpressButtonsSensitive (true);
+               ProButtonsSensitive (true);
+               break;
+       case ContourDesignControlProtocol::ShuttleXpress:
+               _device_state_lbl.set_markup ("<span weight=\"bold\" foreground=\"green\">Found shuttleXpress</span>");
+               XpressButtonsSensitive (true);
+               ProButtonsSensitive (false);
+               break;
+       default:
+               XpressButtonsSensitive (false);
+               ProButtonsSensitive (false);
+               _device_state_lbl.set_markup (string_compose ("<span weight=\"bold\" foreground=\"red\">Device not working:</span> %1",
+                                                             libusb_strerror ((libusb_error)_ccp._error)));
+       }
+
+       return false;
+}
+
+void*
+ContourDesignControlProtocol::get_gui () const
+{
+       if (!_gui) {
+               const_cast<ContourDesignControlProtocol*>(this)->build_gui ();
+       }
+       _gui->show_all();
+       return (void*) _gui;
+}
+
+void
+ContourDesignControlProtocol::build_gui ()
+{
+       _gui = new ContourDesignGUI (*this);
+}
+
+void
+ContourDesignControlProtocol::tear_down_gui ()
+{
+       if (_gui) {
+               Gtk::Widget *w = _gui->get_parent();
+               if (w) {
+                       w->hide();
+                       delete w;
+               }
+       }
+       delete _gui;
+       _gui = 0;
+}
diff --git a/libs/surfaces/contourdesign/interface.cc b/libs/surfaces/contourdesign/interface.cc
new file mode 100644 (file)
index 0000000..b1eb379
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#include "pbd/failed_constructor.h"
+#include "pbd/error.h"
+
+#include "ardour/session.h"
+#include "control_protocol/control_protocol.h"
+
+#include "contourdesign.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_contourdesign_protocol (ControlProtocolDescriptor*, Session* s)
+{
+       ContourDesignControlProtocol* wmcp = new ContourDesignControlProtocol (*s);
+       wmcp->set_active (true);
+       return wmcp;
+}
+
+static void
+delete_contourdesign_protocol (ControlProtocolDescriptor* /*descriptor*/, ControlProtocol* cp)
+{
+       delete cp;
+}
+
+static bool
+probe_contourdesign_protocol (ControlProtocolDescriptor*)
+{
+       return ContourDesignControlProtocol::probe ();
+}
+
+static ControlProtocolDescriptor contourdesign_descriptor = {
+       name : "ContourDesign",
+       id : "uri://ardour.org/surfaces/contourdesign:0",
+       ptr : 0,
+       module : 0,
+       mandatory : 0,
+       supports_feedback : false,
+       probe : probe_contourdesign_protocol,
+       initialize : new_contourdesign_protocol,
+       destroy : delete_contourdesign_protocol
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &contourdesign_descriptor; }
diff --git a/libs/surfaces/contourdesign/jump_distance_widget.cc b/libs/surfaces/contourdesign/jump_distance_widget.cc
new file mode 100644 (file)
index 0000000..1e7955f
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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. 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#include <vector>
+
+#include <gtkmm/spinbutton.h>
+
+#include "gtkmm2ext/utils.h"
+#include "pbd/i18n.h"
+
+#include "jump_distance_widget.h"
+
+
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+using namespace ArdourSurface;
+
+JumpDistanceWidget::JumpDistanceWidget (JumpDistance dist)
+       : HBox ()
+       , _distance (dist)
+       , _value_adj (dist.value, -100, 100, 0.25)
+{
+       SpinButton* sb = manage (new SpinButton (_value_adj, 0.25, 2));
+       sb->signal_value_changed().connect (sigc::mem_fun (*this, &JumpDistanceWidget::update_value));
+       pack_start (*sb);
+
+       vector<string> jog_units_strings;
+       jog_units_strings.push_back (_("seconds"));
+       jog_units_strings.push_back (_("beats"));
+       jog_units_strings.push_back (_("bars"));
+
+       set_popdown_strings (_unit_cb, jog_units_strings);
+       _unit_cb.set_active (_distance.unit);
+       _unit_cb.signal_changed().connect (sigc::mem_fun (*this, &JumpDistanceWidget::update_unit));
+       pack_start (_unit_cb);
+}
+
+void
+JumpDistanceWidget::set_distance (JumpDistance dist)
+{
+       _distance = dist;
+       _value_adj.set_value (dist.value);
+       _unit_cb.set_active (dist.unit);
+}
+
+void
+JumpDistanceWidget::update_unit ()
+{
+       _distance.unit = JumpUnit (_unit_cb.get_active_row_number ());
+       Changed (); /* emit signal */
+}
+
+void
+JumpDistanceWidget::update_value ()
+{
+       _distance.value =  _value_adj.get_value ();
+       Changed (); /* emit signal */
+}
diff --git a/libs/surfaces/contourdesign/jump_distance_widget.h b/libs/surfaces/contourdesign/jump_distance_widget.h
new file mode 100644 (file)
index 0000000..6bc4156
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    Copyright (C) 2019 Paul Davis
+    Author: Johannes Mueller
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+*/
+
+#ifndef ardour_contourdesign_jump_distance_widget_h
+#define ardour_contourdesign_jump_distance_widget_h
+
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/box.h>
+#include <gtkmm/adjustment.h>
+
+#include "pbd/signals.h"
+
+#include "contourdesign.h"
+
+namespace ArdourSurface
+{
+
+class JumpDistanceWidget : public Gtk::HBox
+{
+public:
+       JumpDistanceWidget (JumpDistance dist);
+       ~JumpDistanceWidget () {}
+
+       JumpDistance get_distance () const { return _distance; }
+       void set_distance (JumpDistance dist);
+
+       sigc::signal<void> Changed;
+
+private:
+       JumpDistance _distance;
+
+       void update_value ();
+       void update_unit ();
+
+       Gtk::Adjustment _value_adj;
+       Gtk::ComboBoxText _unit_cb;
+};
+
+} /* namespace */
+
+#endif  /* ardour_contourdesign_jump_distance_widget_h */
diff --git a/libs/surfaces/contourdesign/wscript b/libs/surfaces/contourdesign/wscript
new file mode 100644 (file)
index 0000000..da67c6a
--- /dev/null
@@ -0,0 +1,35 @@
+#!/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 = '''
+            contourdesign.cc
+            contourdesign_gui.cc
+            jump_distance_widget.cc
+            button_config_widget.cc
+            interface.cc
+    '''
+    obj.export_includes = ['./contourdesign']
+    obj.defines      = [ 'PACKAGE="ardour_contourdesign"' ]
+    obj.defines     += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+    obj.includes     = ['.', '../libs', '../../widgets']
+    obj.name         = 'libardour_contourdesign'
+    obj.target       = 'ardour_contourdesign'
+    obj.uselib       = 'GTKMM USB'
+    obj.use          = 'libardour libardour_cp libgtkmm2ext'
+    obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+    autowaf.shutdown()
index e3a32d05bfacce14fd65a8438e66d266f0e2f637..502a2bd50053741f4d0dfdd034782d829291da1a 100644 (file)
@@ -49,8 +49,10 @@ def configure(conf):
 
     if conf.is_defined('HAVE_USB'):
         children += [ 'push2' ]
+        children += [ 'contourdesign' ]
+
     else:
-        print ('You are missing the libusb-1.0 development package needed to compile Push2 support')
+        print ('You are missing the libusb-1.0 development package needed to compile Push2 and ContourDesign support')
 
     if conf.is_defined('HAVE_HIDAPI') and Options.options.maschine:
         children += [ 'maschine2' ]
@@ -96,6 +98,7 @@ def build(bld):
         bld.recurse('tranzport')
     if bld.is_defined('HAVE_USB'):
         bld.recurse('push2')
+        bld.recurse('contourdesign')
     if bld.is_defined('BUILD_MASCHINE'):
         bld.recurse('maschine2')
 
diff --git a/tools/udev/99-counterdesign.rules b/tools/udev/99-counterdesign.rules
new file mode 100644 (file)
index 0000000..90e191f
--- /dev/null
@@ -0,0 +1,7 @@
+# This is a sample udev file to set "ContourDesign ShuttlePRO v2 and
+# shuttleXpress" device permissions on GNU/Linux systems.
+#
+# copy this file to /etc/udev/rules.d/
+
+SUBSYSTEM=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE="660", GROUP="audio"
+SUBSYSTEM=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0020", MODE="660", GROUP="audio"