summaryrefslogtreecommitdiff
path: root/libs/surfaces/contourdesign
diff options
context:
space:
mode:
authorJohannes Mueller <github@johannes-mueller.org>2019-05-18 13:31:24 +0200
committerJohannes Mueller <github@johannes-mueller.org>2019-05-18 14:04:38 +0200
commitb8349069f11db36f13065c55e0eb4e6b5c3fe727 (patch)
tree106965996d5a46ab7146cd2149f8ff446ee707ae /libs/surfaces/contourdesign
parent89f39d14f24648bb3737d83619e199e965825db1 (diff)
Add support for contourdesign ShuttlePRO v2 and ShuttleXpress
Diffstat (limited to 'libs/surfaces/contourdesign')
-rw-r--r--libs/surfaces/contourdesign/button_config_widget.cc272
-rw-r--r--libs/surfaces/contourdesign/button_config_widget.h66
-rw-r--r--libs/surfaces/contourdesign/contourdesign.cc640
-rw-r--r--libs/surfaces/contourdesign/contourdesign.h217
-rw-r--r--libs/surfaces/contourdesign/contourdesign_gui.cc334
-rw-r--r--libs/surfaces/contourdesign/interface.cc65
-rw-r--r--libs/surfaces/contourdesign/jump_distance_widget.cc76
-rw-r--r--libs/surfaces/contourdesign/jump_distance_widget.h58
-rw-r--r--libs/surfaces/contourdesign/wscript35
9 files changed, 1763 insertions, 0 deletions
diff --git a/libs/surfaces/contourdesign/button_config_widget.cc b/libs/surfaces/contourdesign/button_config_widget.cc
new file mode 100644
index 0000000000..17fadc7c9f
--- /dev/null
+++ b/libs/surfaces/contourdesign/button_config_widget.cc
@@ -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
index 0000000000..c1f8f52b78
--- /dev/null
+++ b/libs/surfaces/contourdesign/button_config_widget.h
@@ -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
index 0000000000..b0976f0ddf
--- /dev/null
+++ b/libs/surfaces/contourdesign/contourdesign.cc
@@ -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
index 0000000000..730e89ddaf
--- /dev/null
+++ b/libs/surfaces/contourdesign/contourdesign.h
@@ -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
index 0000000000..88c05234df
--- /dev/null
+++ b/libs/surfaces/contourdesign/contourdesign_gui.cc
@@ -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
index 0000000000..b1eb379a4d
--- /dev/null
+++ b/libs/surfaces/contourdesign/interface.cc
@@ -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
index 0000000000..1e7955fd05
--- /dev/null
+++ b/libs/surfaces/contourdesign/jump_distance_widget.cc
@@ -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
index 0000000000..6bc4156bda
--- /dev/null
+++ b/libs/surfaces/contourdesign/jump_distance_widget.h
@@ -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
index 0000000000..da67c6a66f
--- /dev/null
+++ b/libs/surfaces/contourdesign/wscript
@@ -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()