summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2017-04-05 11:04:16 +0200
committerRobin Gareus <robin@gareus.org>2017-04-13 21:21:59 +0200
commitd43a23fe28c6f54ed8ca6cde6497661c6cb73ce0 (patch)
treed89166f438d4a3d42034d6b106506cb892d2f068
parentd64ca9be08331756e936018ea4d06404faa2ca90 (diff)
Faderport8 control surface support
-rw-r--r--gtk2_ardour/ardev_common.sh.in2
-rw-r--r--libs/ardour/ardour/debug.h1
-rw-r--r--libs/ardour/debug.cc1
-rw-r--r--libs/ardour/port_manager.cc1
-rw-r--r--libs/surfaces/faderport8/actions.cc470
-rw-r--r--libs/surfaces/faderport8/callbacks.cc206
-rw-r--r--libs/surfaces/faderport8/faderport8.cc1501
-rw-r--r--libs/surfaces/faderport8/faderport8.h334
-rw-r--r--libs/surfaces/faderport8/faderport8_interface.cc81
-rw-r--r--libs/surfaces/faderport8/fp8_base.h151
-rw-r--r--libs/surfaces/faderport8/fp8_button.h490
-rw-r--r--libs/surfaces/faderport8/fp8_controls.cc413
-rw-r--r--libs/surfaces/faderport8/fp8_controls.h172
-rw-r--r--libs/surfaces/faderport8/fp8_strip.cc524
-rw-r--r--libs/surfaces/faderport8/fp8_strip.h162
-rw-r--r--libs/surfaces/faderport8/gui.cc424
-rw-r--r--libs/surfaces/faderport8/gui.h98
-rw-r--r--libs/surfaces/faderport8/wscript37
-rw-r--r--libs/surfaces/wscript2
19 files changed, 5069 insertions, 1 deletions
diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in
index b785e36a10..06dc9d0973 100644
--- a/gtk2_ardour/ardev_common.sh.in
+++ b/gtk2_ardour/ardev_common.sh.in
@@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
# can find all the components.
#
-export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2
+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/wiimote:$libs/surfaces/push2
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:.
diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h
index 0b7e5fefb5..eff8305bf1 100644
--- a/libs/ardour/ardour/debug.h
+++ b/libs/ardour/ardour/debug.h
@@ -80,6 +80,7 @@ namespace PBD {
LIBARDOUR_API extern DebugBits BackendPorts;
LIBARDOUR_API extern DebugBits VSTCallbacks;
LIBARDOUR_API extern DebugBits FaderPort;
+ LIBARDOUR_API extern DebugBits FaderPort8;
LIBARDOUR_API extern DebugBits CC121;
LIBARDOUR_API extern DebugBits VCA;
LIBARDOUR_API extern DebugBits Push2;
diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc
index 8c88dd4d1d..5c645a8095 100644
--- a/libs/ardour/debug.cc
+++ b/libs/ardour/debug.cc
@@ -77,6 +77,7 @@ PBD::DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("backendthreads"
PBD::DebugBits PBD::DEBUG::BackendPorts = PBD::new_debug_bit ("backendports");
PBD::DebugBits PBD::DEBUG::VSTCallbacks = PBD::new_debug_bit ("vstcallbacks");
PBD::DebugBits PBD::DEBUG::FaderPort = PBD::new_debug_bit ("faderport");
+PBD::DebugBits PBD::DEBUG::FaderPort8 = PBD::new_debug_bit ("faderport8");
PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc
index b99337a891..aa56ab8d43 100644
--- a/libs/ardour/port_manager.cc
+++ b/libs/ardour/port_manager.cc
@@ -896,6 +896,7 @@ PortManager::port_is_control_only (std::string const& name)
const char * const control_only_ports[] = {
X_(".*Ableton Push.*"),
X_(".*FaderPort .*"),
+ X_(".*FaderPort8 .*"),
};
pattern = "(";
diff --git a/libs/surfaces/faderport8/actions.cc b/libs/surfaces/faderport8/actions.cc
new file mode 100644
index 0000000000..949d19a958
--- /dev/null
+++ b/libs/surfaces/faderport8/actions.cc
@@ -0,0 +1,470 @@
+/* Faderport 8 Control Surface
+ * This is the button "Controller" of the MVC surface inteface,
+ * see callbacks.cc for the "View".
+ *
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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 "ardour/dB.h"
+#include "ardour/session.h"
+#include "ardour/session_configuration.h"
+#include "ardour/types.h"
+
+#include "gtkmm2ext/actions.h"
+
+#include "faderport8.h"
+
+#include "pbd/i18n.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace std;
+using namespace ArdourSurface::FP8Types;
+
+#define BindMethod(ID, CB) \
+ _ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this));
+
+#define BindFunction(ID, ACT, CB, ...) \
+ _ctrls.button (FP8Controls::ID). ACT .connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this, __VA_ARGS__));
+
+#define BindAction(ID, GRP, ITEM) \
+ _ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_action, this, GRP, ITEM));
+
+#define BindUserAction(ID) \
+ _ctrls.button (ID).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, true, ID)); \
+_ctrls.button (ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, false, ID));
+
+void
+FaderPort8::setup_actions ()
+{
+ BindMethod (BtnPlay, button_play);
+ BindMethod (BtnStop, button_stop);
+ BindMethod (BtnLoop, button_loop);
+ BindMethod (BtnRecord, button_record);
+ BindMethod (BtnClick, button_metronom);
+ BindAction (BtnRedo, "Editor", "redo");
+
+ BindAction (BtnSave, "Common", "Save");
+ BindAction (BtnUndo, "Editor", "undo");
+ BindAction (BtnRedo, "Editor", "redo");
+
+ BindAction (BtnSoloClear, "Main", "cancel-solo");
+ BindMethod (BtnMuteClear, button_mute_clear);
+
+ BindMethod (FP8Controls::BtnArmAll, button_arm_all);
+
+ BindFunction (BtnRewind, pressed, button_varispeed, false);
+ BindFunction (BtnFastForward, pressed, button_varispeed, true);
+
+ BindFunction (BtnPrev, released, button_prev_next, false);
+ BindFunction (BtnNext, released, button_prev_next, true);
+
+ BindFunction (BtnArm, pressed, button_arm, true);
+ BindFunction (BtnArm, released, button_arm, false);
+
+ BindFunction (BtnAOff, released, button_automation, ARDOUR::Off);
+ BindFunction (BtnATouch, released, button_automation, ARDOUR::Touch);
+ BindFunction (BtnARead, released, button_automation, ARDOUR::Play);
+ BindFunction (BtnAWrite, released, button_automation, ARDOUR::Write);
+
+ _ctrls.button (FP8Controls::BtnEncoder).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_encoder, this));
+ _ctrls.button (FP8Controls::BtnParam).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_parameter, this));
+
+
+ BindAction (BtnBypass, "Mixer", "ab-plugins");
+ BindAction (BtnBypassAll, "Mixer", "ab-plugins"); // XXX
+
+ BindAction (BtnMacro, "Mixer", "show-editor");
+ BindAction (BtnLink, "Window", "show-mixer");
+
+ BindAction (BtnOpen, "Common", "addExistingAudioFiles");
+ BindAction (BtnLock, "Editor", "lock");
+
+ // user-specific
+ for (FP8Controls::UserButtonMap::const_iterator i = _ctrls.user_buttons ().begin ();
+ i != _ctrls.user_buttons ().end (); ++i) {
+ BindUserAction ((*i).first);
+ }
+}
+
+void
+FaderPort8::button_play ()
+{
+ if (session->transport_rolling ()) {
+ if (session->transport_speed () != 1.0) {
+ session->request_transport_speed (1.0);
+ } else {
+ transport_stop ();
+ }
+ } else {
+ transport_play ();
+ }
+}
+
+void
+FaderPort8::button_stop ()
+{
+ transport_stop ();
+}
+
+void
+FaderPort8::button_record ()
+{
+ set_record_enable (!get_record_enabled ());
+}
+
+void
+FaderPort8::button_loop ()
+{
+ loop_toggle ();
+}
+
+void
+FaderPort8::button_metronom ()
+{
+ Config->set_clicking (!Config->get_clicking ());
+}
+
+void
+FaderPort8::button_automation (ARDOUR::AutoState as)
+{
+ FaderMode fadermode = _ctrls.fader_mode ();
+ switch (fadermode) {
+ case ModePlugins:
+#if 0 // Plugin Control Automation Mode
+ for ( std::list <ProcessorCtrl>::iterator i = _proc_params.begin(); i != _proc_params.end(); ++i) {
+ ((*i).ac)->set_automation_state (as);
+ }
+#endif
+ return;
+ case ModeSend:
+ if (first_selected_stripable()) {
+#if 0 // Send Level Automation
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ boost::shared_ptr<AutomationControl> send;
+ uint32_t i = 0;
+ while (0 != (send = s->send_level_controllable (i))) {
+ send->set_automation_state (as);
+ ++i;
+ }
+#endif
+ }
+ return;
+ default:
+ break;
+ }
+
+ // apply to all selected tracks
+ StripableList all;
+ session->get_stripables (all);
+ for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
+ if ((*i)->is_master() || (*i)->is_monitor()) {
+ continue;
+ }
+ if (!(*i)->is_selected()) {
+ continue;
+ }
+ boost::shared_ptr<AutomationControl> ac;
+ switch (fadermode) {
+ case ModeTrack:
+ ac = (*i)->gain_control ();
+ break;
+ case ModePan:
+ ac = (*i)->pan_azimuth_control ();
+ break;
+ default:
+ break;
+ }
+ if (ac) {
+ ac->set_automation_state (as);
+ }
+ }
+}
+
+void
+FaderPort8::button_varispeed (bool ffw)
+{
+ /* pressing both rew + ffwd -> return to zero */
+ FP8ButtonInterface& b_rew = _ctrls.button (FP8Controls::BtnRewind);
+ FP8ButtonInterface& b_ffw = _ctrls.button (FP8Controls::BtnFastForward);
+ if (b_rew.is_pressed () && b_ffw.is_pressed ()){
+ // stop key-repeat
+ dynamic_cast<FP8RepeatButton*>(&b_ffw)->stop_repeat();
+ dynamic_cast<FP8RepeatButton*>(&b_rew)->stop_repeat();
+ AccessAction ("Transport", "GotoStart");
+ return;
+ }
+
+ // switch play direction, if needed
+ if (ffw) {
+ if (session->transport_speed () <= 0) {
+ session->request_transport_speed (1.0);
+ return ;
+ }
+ } else {
+ if (session->transport_speed () >= 0) {
+ session->request_transport_speed (-1.0);
+ return ;
+ }
+ }
+ // incremetally increase speed. double speed every 10 clicks
+ // (keypress auto-repeat is 100ms)
+ float maxspeed = Config->get_shuttle_max_speed();
+ float speed = exp2f(0.1f) * session->transport_speed ();
+ speed = std::max (-maxspeed, std::min (maxspeed, speed));
+ session->request_transport_speed (speed, false);
+}
+
+void
+FaderPort8::button_mute_clear ()
+{
+ StripableList all;
+ session->get_stripables (all);
+ boost::shared_ptr<ControlList> cl (new ControlList);
+ for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
+ if ((*i)->is_master() || (*i)->is_monitor()) {
+ continue;
+ }
+ boost::shared_ptr<AutomationControl> ac = (*i)->mute_control();
+ if (ac) {
+ cl->push_back (ac);
+ }
+ }
+ session->set_controls (cl, 0.0, PBD::Controllable::UseGroup);
+}
+
+void
+FaderPort8::button_arm (bool press)
+{
+ FaderMode fadermode = _ctrls.fader_mode ();
+ if (fadermode == ModeTrack || fadermode == ModePan) {
+ _ctrls.button (FP8Controls::BtnArm).set_active (press);
+ ARMButtonChange (press);
+ }
+}
+
+void
+FaderPort8::button_arm_all ()
+{
+ BasicUI::all_tracks_rec_in ();
+}
+
+void
+FaderPort8::button_action (const std::string& group, const std::string& item)
+{
+ AccessAction (group, item);
+}
+
+void
+FaderPort8::button_prev_next (bool next)
+{
+ switch (_ctrls.nav_mode()) {
+ case NavMaster:
+ case NavChannel:
+ case NavScroll:
+ bank (!next, false);
+ break;
+ case NavBank:
+ bank (!next, true);
+ break;
+ case NavZoom:
+ if (next) {
+ StepTracksDown ();
+ } else {
+ StepTracksUp ();
+ }
+ break;
+ case NavSection:
+ // TODO nudge
+ break;
+ case NavMarker:
+ if (next) {
+ next_marker ();
+ } else {
+ prev_marker ();
+ }
+ break;
+ }
+}
+
+/* handle navigation encoder press */
+void
+FaderPort8::button_encoder ()
+{
+ switch (_ctrls.nav_mode()) {
+ case NavZoom:
+ ZoomToSession (); // XXX undo zoom
+ break;
+ case NavScroll:
+ ZoomToSession ();
+ break;
+ case NavChannel:
+ case NavBank:
+ move_selected_into_view ();
+ break;
+ case NavMaster:
+ {
+ /* master || monitor level -- reset to 0dB */
+ boost::shared_ptr<AutomationControl> ac;
+ if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
+ ac = session->monitor_out()->gain_control ();
+ } else if (session->master_out()) {
+ ac = session->master_out()->gain_control ();
+ }
+ if (ac) {
+ ac->set_value (ac->normal(), PBD::Controllable::NoGroup);
+ }
+ }
+ break;
+ case NavSection:
+ break;
+ case NavMarker:
+ {
+ string markername;
+ /* Don't add another mark if one exists within 1/100th of a second of
+ * the current position and we're not rolling.
+ */
+ framepos_t where = session->audible_frame();
+ if (session->transport_stopped() && session->locations()->mark_at (where, session->frame_rate() / 100.0)) {
+ return;
+ }
+
+ session->locations()->next_available_name (markername,"mark");
+ add_marker (markername);
+ }
+ break;
+ }
+}
+
+/* handle navigation encoder turn */
+void
+FaderPort8::encoder_navigate (bool neg, int steps)
+{
+ /* special-case metronome level */
+ if (_ctrls.button (FP8Controls::BtnClick).is_pressed ()) {
+ // compare to ARDOUR_UI::click_button_scroll()
+ gain_t gain = Config->get_click_gain();
+ float gain_db = accurate_coefficient_to_dB (gain);
+ gain_db += (neg ? -1.f : 1.f) * steps;
+ gain_db = std::max (-60.f, gain_db);
+ gain = dB_to_coefficient (gain_db);
+ gain = std::min (gain, Config->get_max_gain());
+ Config->set_click_gain (gain);
+ _ctrls.button (FP8Controls::BtnClick).ignore_release();
+ return;
+ }
+
+ switch (_ctrls.nav_mode()) {
+ case NavChannel:
+ if (neg) {
+ StepTracksUp ();
+ } else {
+ StepTracksDown ();
+ }
+ break;
+ case NavZoom:
+ if (neg) {
+ ZoomOut ();
+ } else {
+ ZoomIn ();
+ }
+ break;
+ case NavMarker:
+ case NavScroll:
+ ScrollTimeline ((neg ? -1.f : 1.f) * steps / (shift_mod() ? 1024.f : 256.f));
+ break;
+ case NavBank:
+ bank (neg, false);
+ break;
+ case NavMaster:
+ {
+ /* master || monitor level */
+ boost::shared_ptr<AutomationControl> ac;
+ if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
+ ac = session->monitor_out()->gain_control ();
+ } else if (session->master_out()) {
+ ac = session->master_out()->gain_control ();
+ }
+ if (ac) {
+ double v = ac->internal_to_interface (ac->get_value());
+ v = std::max (0.0, std::min (1.0, v + steps * (neg ? -.01 : .01)));
+ ac->set_value (ac->interface_to_internal(v), PBD::Controllable::NoGroup);
+ }
+ }
+ break;
+ case NavSection:
+ // nudge event
+ break;
+ }
+}
+
+/* handle pan/param encoder press */
+void
+FaderPort8::button_parameter ()
+{
+ switch (_ctrls.fader_mode()) {
+ case ModeTrack:
+ case ModePan:
+ // pan-width see FaderPort8::encoder_parameter()
+ break;
+ case ModePlugins:
+ break;
+ case ModeSend:
+ break;
+ }
+}
+
+/* handle pan/param encoder turn */
+void
+FaderPort8::encoder_parameter (bool neg, int steps)
+{
+ switch (_ctrls.fader_mode()) {
+ case ModeTrack:
+ case ModePan:
+ {
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ if (s) {
+ boost::shared_ptr<AutomationControl> ac;
+ if (_ctrls.button (FP8Controls::BtnParam).is_pressed ()) {
+ ac = s->pan_width_control ();
+ } else {
+ ac = s->pan_azimuth_control ();
+ }
+ if (ac) {
+ double v = ac->internal_to_interface (ac->get_value());
+ v = std::max (0.0, std::min (1.0, v + steps * (neg ? -.01 : .01)));
+ ac->set_value (ac->interface_to_internal(v), PBD::Controllable::UseGroup);
+ }
+ }
+ }
+ break;
+ case ModePlugins:
+ case ModeSend:
+ while (steps > 0) {
+ bank_param (neg, false);
+ --steps;
+ }
+ break;
+ }
+}
+
+/* handle user-specific actions */
+void
+FaderPort8::button_user (bool press, FP8Controls::ButtonId btn)
+{
+ _user_action_map[btn].call (*this, press);
+}
diff --git a/libs/surfaces/faderport8/callbacks.cc b/libs/surfaces/faderport8/callbacks.cc
new file mode 100644
index 0000000000..72aa8228bf
--- /dev/null
+++ b/libs/surfaces/faderport8/callbacks.cc
@@ -0,0 +1,206 @@
+/* Faderport 8 Control Surface
+ * This is the button "View" of the MVC surface inteface,
+ * see actions.cc for the "Controller"
+ *
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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 "ardour/session.h"
+#include "ardour/session_configuration.h"
+
+#include "gtkmm2ext/actions.h"
+
+#include "faderport8.h"
+
+#include "pbd/i18n.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace ArdourSurface::FP8Types;
+
+void
+FaderPort8::connect_session_signals ()
+{
+ session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_stripable_added_or_removed, this), this);
+ PresentationInfo::Change.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_pi_property_changed, this, _1), this);
+
+ Config->ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_parameter_changed, this, _1), this);
+ session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_parameter_changed, this, _1), this);
+
+ session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_transport_state_changed, this), this);
+ session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_loop_state_changed, this), this);
+ session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_record_state_changed, this), this);
+
+ session->DirtyChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_session_dirty_changed, this), this);
+ session->SoloChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_solo_changed, this), this);
+ session->MuteChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_mute_changed, this), this);
+ session->history().Changed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_history_changed, this), this);
+}
+
+void
+FaderPort8::send_session_state ()
+{
+ notify_transport_state_changed ();
+ notify_record_state_changed ();
+ notify_session_dirty_changed ();
+ notify_history_changed ();
+ notify_solo_changed ();
+ notify_mute_changed ();
+ notify_parameter_changed ("clicking");
+
+ notify_automation_mode_changed (); // XXX (stip specific, see below)
+}
+
+// TODO: AutomationState display of plugin & send automation ?!
+void
+FaderPort8::notify_automation_mode_changed ()
+{
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ boost::shared_ptr<AutomationControl> ac;
+ if (s) {
+ switch (_ctrls.fader_mode ()) {
+ case ModeTrack:
+ ac = s->gain_control();
+ break;
+ case ModePan:
+ ac = s->pan_azimuth_control();
+ break;
+ default:
+ break;
+ }
+ }
+ if (!s || !ac) {
+ _ctrls.button (FP8Controls::BtnALatch).set_active (false);
+ _ctrls.button (FP8Controls::BtnATrim).set_active (false);
+ _ctrls.button (FP8Controls::BtnAOff).set_active (false);
+ _ctrls.button (FP8Controls::BtnATouch).set_active (false);
+ _ctrls.button (FP8Controls::BtnARead).set_active (false);
+ _ctrls.button (FP8Controls::BtnAWrite).set_active (false);
+ return;
+ }
+
+ ARDOUR::AutoState as = ac->automation_state();
+ _ctrls.button (FP8Controls::BtnAOff).set_active (as == Off);
+ _ctrls.button (FP8Controls::BtnATouch).set_active (as == Touch);
+ _ctrls.button (FP8Controls::BtnARead).set_active (as == Play);
+ _ctrls.button (FP8Controls::BtnAWrite).set_active (as == Write);
+}
+
+void
+FaderPort8::notify_parameter_changed (std::string param)
+{
+ if (param == "clicking") {
+ _ctrls.button (FP8Controls::BtnClick).set_active (Config->get_clicking ());
+ }
+}
+
+void
+FaderPort8::notify_transport_state_changed ()
+{
+ if (session->transport_rolling ()) {
+ _ctrls.button (FP8Controls::BtnPlay).set_active (true);
+ _ctrls.button (FP8Controls::BtnStop).set_active (false);
+ } else {
+ _ctrls.button (FP8Controls::BtnPlay).set_active (false);
+ _ctrls.button (FP8Controls::BtnStop).set_active (true);
+ }
+
+ /* set rewind/fastforward lights */
+ const float ts = session->transport_speed ();
+ FP8ButtonInterface& b_rew = _ctrls.button (FP8Controls::BtnRewind);
+ FP8ButtonInterface& b_ffw = _ctrls.button (FP8Controls::BtnFastForward);
+
+ const bool rew = (ts < 0.f);
+ const bool ffw = (ts > 0.f && ts != 1.f);
+ if (b_rew.is_active() != rew) {
+ b_rew.set_active (rew);
+ }
+ if (b_ffw.is_active() != ffw) {
+ b_ffw.set_active (ffw);
+ }
+
+ notify_loop_state_changed ();
+}
+
+void
+FaderPort8::notify_record_state_changed ()
+{
+ switch (session->record_status ()) {
+ case Session::Disabled:
+ _ctrls.button (FP8Controls::BtnRecord).set_active (0);
+ _ctrls.button (FP8Controls::BtnRecord).set_blinking (false);
+ break;
+ case Session::Enabled:
+ _ctrls.button (FP8Controls::BtnRecord).set_active (true);
+ _ctrls.button (FP8Controls::BtnRecord).set_blinking (true);
+ break;
+ case Session::Recording:
+ _ctrls.button (FP8Controls::BtnRecord).set_active (true);
+ _ctrls.button (FP8Controls::BtnRecord).set_blinking (false);
+ break;
+ }
+}
+
+void
+FaderPort8::notify_loop_state_changed ()
+{
+ bool looping = false;
+ Location* looploc = session->locations ()->auto_loop_location ();
+ if (looploc && session->get_play_loop ()) {
+ looping = true;
+ }
+ _ctrls.button (FP8Controls::BtnLoop).set_active (looping);
+}
+
+void
+FaderPort8::notify_session_dirty_changed ()
+{
+ const bool is_dirty = session->dirty ();
+ _ctrls.button (FP8Controls::BtnSave).set_active (is_dirty);
+ _ctrls.button (FP8Controls::BtnSave).set_color (is_dirty ? 0xff0000ff : 0x00ff00ff);
+}
+
+void
+FaderPort8::notify_history_changed ()
+{
+ _ctrls.button (FP8Controls::BtnRedo).set_active (session->redo_depth() > 0);
+ _ctrls.button (FP8Controls::BtnUndo).set_active (session->undo_depth() > 0);
+}
+
+void
+FaderPort8::notify_solo_changed ()
+{
+ _ctrls.button (FP8Controls::BtnSoloClear).set_active (session->soloing() || session->listening());
+}
+
+void
+FaderPort8::notify_mute_changed ()
+{
+ bool muted = false;
+ boost::shared_ptr<RouteList> rl = session->get_routes();
+ for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+ if ((*i)->is_master() || (*i)->is_monitor()) {
+ continue;
+ }
+ boost::shared_ptr<MuteControl> mc = (*i)->mute_control();
+ if (mc && mc->muted ()) {
+ muted = true;
+ break;
+ }
+ }
+ _ctrls.button (FP8Controls::BtnMuteClear).set_active (muted);
+}
diff --git a/libs/surfaces/faderport8/faderport8.cc b/libs/surfaces/faderport8/faderport8.cc
new file mode 100644
index 0000000000..424742aef0
--- /dev/null
+++ b/libs/surfaces/faderport8/faderport8.cc
@@ -0,0 +1,1501 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <cstdlib>
+#include <sstream>
+#include <algorithm>
+
+#include <stdint.h>
+
+#include "pbd/error.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/compose.h"
+#include "pbd/xml++.h"
+
+#include "midi++/port.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/audio_track.h"
+#include "ardour/bundle.h"
+#include "ardour/debug.h"
+#include "ardour/midi_track.h"
+#include "ardour/midiport_manager.h"
+#include "ardour/plugin_insert.h"
+#include "ardour/processor.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/route.h"
+#include "ardour/session.h"
+#include "ardour/session_configuration.h"
+#include "ardour/vca.h"
+
+#include "faderport8.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace PBD;
+using namespace Glib;
+using namespace std;
+using namespace ArdourSurface::FP8Types;
+
+#include "pbd/i18n.h"
+
+#include "pbd/abstract_ui.cc" // instantiate template
+
+#ifndef NDEBUG
+//#define VERBOSE_DEBUG
+#endif
+
+static void
+debug_2byte_msg (std::string const& msg, int b0, int b1)
+{
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::FaderPort8)) {
+ DEBUG_STR_DECL(a);
+ DEBUG_STR_APPEND(a, "RECV: ");
+ DEBUG_STR_APPEND(a, msg);
+ DEBUG_STR_APPEND(a,' ');
+ DEBUG_STR_APPEND(a,hex);
+ DEBUG_STR_APPEND(a,"0x");
+ DEBUG_STR_APPEND(a, b0);
+ DEBUG_STR_APPEND(a,' ');
+ DEBUG_STR_APPEND(a,"0x");
+ DEBUG_STR_APPEND(a, b1);
+ DEBUG_STR_APPEND(a,'\n');
+ DEBUG_TRACE (DEBUG::FaderPort8, DEBUG_STR(a).str());
+ }
+#endif
+}
+
+FaderPort8::FaderPort8 (Session& s)
+ : ControlProtocol (s, _("PreSonus FaderPort8"))
+ , AbstractUI<FaderPort8Request> (name())
+ , _connection_state (ConnectionState (0))
+ , _device_active (false)
+ , _ctrls (*this)
+ , _channel_off (0)
+ , _plugin_off (0)
+ , _parameter_off (0)
+ , _blink_onoff (false)
+ , _shift_lock (false)
+ , _shift_pressed (false)
+ , gui (0)
+{
+ boost::shared_ptr<ARDOUR::Port> inp;
+ boost::shared_ptr<ARDOUR::Port> outp;
+
+ inp = AudioEngine::instance()->register_input_port (DataType::MIDI, "FaderPort8 Recv", true);
+ outp = AudioEngine::instance()->register_output_port (DataType::MIDI, "FaderPort8 Send", true);
+ _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp);
+ _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp);
+
+ if (_input_port == 0 || _output_port == 0) {
+ throw failed_constructor();
+ }
+
+ _input_bundle.reset (new ARDOUR::Bundle (_("FaderPort8 (Receive)"), true));
+ _output_bundle.reset (new ARDOUR::Bundle (_("FaderPort8 (Send) "), false));
+
+ _input_bundle->add_channel (
+ inp->name(),
+ ARDOUR::DataType::MIDI,
+ session->engine().make_port_name_non_relative (inp->name())
+ );
+
+ _output_bundle->add_channel (
+ outp->name(),
+ ARDOUR::DataType::MIDI,
+ session->engine().make_port_name_non_relative (outp->name())
+ );
+
+ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort8::connection_handler, this, _1, _2, _3, _4, _5), this);
+
+ StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort8::gui_track_selection_changed, this), this);
+
+ setup_actions ();
+ _ctrls.FaderModeChanged.connect_same_thread (modechange_connections, boost::bind (&FaderPort8::notify_fader_mode_changed, this));
+ _ctrls.MixModeChanged.connect_same_thread (modechange_connections, boost::bind (&FaderPort8::assign_strips, this, true));
+}
+
+FaderPort8::~FaderPort8 ()
+{
+ cerr << "~FP8\n";
+ stop_midi_handling ();
+ close ();
+
+ if (_input_port) {
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
+ AudioEngine::instance()->unregister_port (_input_port);
+ _input_port.reset ();
+ }
+
+ if (_output_port) {
+ _output_port->drain (10000, 250000); /* check every 10 msecs, wait up to 1/4 second for the port to drain */
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name()));
+ AudioEngine::instance()->unregister_port (_output_port);
+ _output_port.reset ();
+ }
+
+ tear_down_gui ();
+
+ /* stop event loop */
+ DEBUG_TRACE (DEBUG::FaderPort8, "BaseUI::quit ()\n");
+ BaseUI::quit ();
+}
+
+/* ****************************************************************************
+ * Event Loop
+ */
+
+void*
+FaderPort8::request_factory (uint32_t num_requests)
+{
+ /* AbstractUI<T>::request_buffer_factory() is a template method only
+ * instantiated in this source module. To provide something visible for
+ * use in the interface/descriptor, we have this static method that is
+ * template-free.
+ */
+ return request_buffer_factory (num_requests);
+}
+
+void
+FaderPort8::do_request (FaderPort8Request* req)
+{
+ if (req->type == CallSlot) {
+ call_slot (MISSING_INVALIDATOR, req->the_slot);
+ } else if (req->type == Quit) {
+ stop ();
+ }
+}
+
+int
+FaderPort8::stop ()
+{
+ BaseUI::quit ();
+ return 0;
+}
+
+void
+FaderPort8::thread_init ()
+{
+ struct sched_param rtparam;
+
+ pthread_set_name (event_loop_name().c_str());
+
+ PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+ ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+ memset (&rtparam, 0, sizeof (rtparam));
+ rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
+
+ if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
+ // do we care? not particularly.
+ }
+}
+
+bool
+FaderPort8::periodic ()
+{
+ /* prepare TC display -- handled by stripable Periodic () */
+ if (_ctrls.display_timecode ()) {
+ // TODO allow BBT, HHMMSS
+ // used in FP8Strip::periodic_update_timecode
+ Timecode::Time TC;
+ session->timecode_time (TC);
+ _timecode = Timecode::timecode_format_time(TC);
+ } else {
+ _timecode.clear ();
+ }
+
+ /* update stripables */
+ Periodic ();
+ return true;
+}
+
+bool
+FaderPort8::blink_it ()
+{
+ _blink_onoff = !_blink_onoff;
+ BlinkIt (_blink_onoff);
+ return true;
+}
+
+/* ****************************************************************************
+ * Port and Signal Connection Management
+ */
+int
+FaderPort8::set_active (bool yn)
+{
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose("set_active init with yn: '%1'\n", yn));
+
+ if (yn == active()) {
+ return 0;
+ }
+
+ if (yn) {
+ /* start event loop */
+ BaseUI::run ();
+ connect_session_signals ();
+ } else {
+ BaseUI::quit ();
+ close ();
+ }
+
+ ControlProtocol::set_active (yn);
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose("set_active done with yn: '%1'\n", yn));
+ return 0;
+}
+
+void
+FaderPort8::close ()
+{
+ _assigned_strips.clear ();
+ stop_midi_handling ();
+ session_connections.drop_connections ();
+ automation_state_connections.drop_connections ();
+ assigned_stripable_connections.drop_connections ();
+ drop_ctrl_connections ();
+ port_connection.disconnect ();
+ selection_connection.disconnect ();
+}
+
+void
+FaderPort8::stop_midi_handling ()
+{
+ _periodic_connection.disconnect ();
+ _blink_connection.disconnect ();
+ midi_connections.drop_connections ();
+ /* Note: the input handler is still active at this point, but we're no
+ * longer connected to any of the parser signals
+ */
+}
+
+void
+FaderPort8::connected ()
+{
+ DEBUG_TRACE (DEBUG::FaderPort8, "initializing\n");
+ // ideally check firmware version >= 1.01 (USB bcdDevice 0x0101) (vendor 0x194f prod 0x0202)
+ // but we don't have a handle to the underlying USB device here.
+
+ _channel_off = _plugin_off = _parameter_off = 0;
+ _blink_onoff = false;
+ _shift_lock = false;
+ _shift_pressed = false;
+
+ start_midi_handling ();
+ _ctrls.initialize ();
+
+ /* highlight bound user-actions */
+ for (FP8Controls::UserButtonMap::const_iterator i = _ctrls.user_buttons ().begin ();
+ i != _ctrls.user_buttons ().end (); ++i) {
+ _ctrls.button (i->first).set_active (! _user_action_map[i->first].empty ());
+ }
+ /* shift button lights */
+ tx_midi3 (0x90, 0x06, 0x00);
+ tx_midi3 (0x90, 0x46, 0x00);
+
+ send_session_state ();
+ assign_strips (true);
+
+ Glib::RefPtr<Glib::TimeoutSource> blink_timer =
+ Glib::TimeoutSource::create (200);
+ _blink_connection = blink_timer->connect (sigc::mem_fun (*this, &FaderPort8::blink_it));
+ blink_timer->attach (main_loop()->get_context());
+
+ Glib::RefPtr<Glib::TimeoutSource> periodic_timer =
+ Glib::TimeoutSource::create (100);
+ _periodic_connection = periodic_timer->connect (sigc::mem_fun (*this, &FaderPort8::periodic));
+ periodic_timer->attach (main_loop()->get_context());
+}
+
+void
+FaderPort8::disconnected ()
+{
+ stop_midi_handling ();
+ for (uint8_t id = 0; id < 8; ++id) {
+ _ctrls.strip(id).unset_controllables ();
+ }
+}
+
+bool
+FaderPort8::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+#ifdef VERBOSE_DEBUG
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::connection_handler: start\n");
+#endif
+ if (!_input_port || !_output_port) {
+ return false;
+ }
+
+ string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name());
+ string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name());
+
+ if (ni == name1 || ni == name2) {
+ if (yn) {
+ _connection_state |= InputConnected;
+ } else {
+ _connection_state &= ~InputConnected;
+ }
+ } else if (no == name1 || no == name2) {
+ if (yn) {
+ _connection_state |= OutputConnected;
+ } else {
+ _connection_state &= ~OutputConnected;
+ }
+ } else {
+#ifdef VERBOSE_DEBUG
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+#endif
+ /* not our ports */
+ return false;
+ }
+
+ if ((_connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+
+ /* XXX this is a horrible hack. Without a short sleep here,
+ * something prevents the device wakeup messages from being
+ * sent and/or the responses from being received.
+ */
+ g_usleep (100000);
+ DEBUG_TRACE (DEBUG::FaderPort8, "device now connected for both input and output\n");
+ connected ();
+ _device_active = true;
+
+ } else {
+ DEBUG_TRACE (DEBUG::FaderPort8, "Device disconnected (input or output or both) or not yet fully connected\n");
+ disconnected ();
+ _device_active = false;
+ }
+
+ ConnectionChange (); /* emit signal for our GUI */
+
+#ifdef VERBOSE_DEBUG
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::connection_handler: end\n");
+#endif
+
+ return true; /* connection status changed */
+}
+
+list<boost::shared_ptr<ARDOUR::Bundle> >
+FaderPort8::bundles ()
+{
+ list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+ if (_input_bundle) {
+ b.push_back (_input_bundle);
+ b.push_back (_output_bundle);
+ }
+
+ return b;
+}
+
+/* ****************************************************************************
+ * MIDI I/O
+ */
+bool
+FaderPort8::midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::AsyncMIDIPort> wport)
+{
+ boost::shared_ptr<AsyncMIDIPort> port (wport.lock());
+
+ if (!port) {
+ return false;
+ }
+
+#ifdef VERBOSE_DEBUG
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("something happend on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+#endif
+
+ if (ioc & ~IO_IN) {
+ return false;
+ }
+
+ if (ioc & IO_IN) {
+
+ port->clear ();
+#ifdef VERBOSE_DEBUG
+ DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+#endif
+ framepos_t now = session->engine().sample_time();
+ port->parse (now);
+ }
+
+ return true;
+}
+
+void
+FaderPort8::start_midi_handling ()
+{
+ _input_port->parser()->sysex.connect_same_thread (midi_connections, boost::bind (&FaderPort8::sysex_handler, this, _1, _2, _3));
+ _input_port->parser()->poly_pressure.connect_same_thread (midi_connections, boost::bind (&FaderPort8::polypressure_handler, this, _1, _2));
+ for (uint8_t i = 0; i < 16; ++i) {
+ _input_port->parser()->channel_pitchbend[i].connect_same_thread (midi_connections, boost::bind (&FaderPort8::pitchbend_handler, this, _1, i, _2));
+ }
+ _input_port->parser()->controller.connect_same_thread (midi_connections, boost::bind (&FaderPort8::controller_handler, this, _1, _2));
+ _input_port->parser()->note_on.connect_same_thread (midi_connections, boost::bind (&FaderPort8::note_on_handler, this, _1, _2));
+ _input_port->parser()->note_off.connect_same_thread (midi_connections, boost::bind (&FaderPort8::note_off_handler, this, _1, _2));
+
+ /* This connection means that whenever data is ready from the input
+ * port, the relevant thread will invoke our ::midi_input_handler()
+ * method, which will read the data, and invoke the parser.
+ */
+ _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &FaderPort8::midi_input_handler), boost::weak_ptr<AsyncMIDIPort> (_input_port)));
+ _input_port->xthread().attach (main_loop()->get_context());
+}
+
+size_t
+FaderPort8::tx_midi (std::vector<uint8_t> const& d) const
+{
+ /* work around midi buffer overflow for batch changes */
+ if (d.size() == 3 && (d[0] == 0x91 || d[0] == 0x92)) {
+ /* set colors triplet in one go */
+ } else if (d.size() == 3 && (d[0] == 0x93)) {
+ g_usleep (1500);
+ } else {
+ g_usleep (400 * d.size());
+ }
+#ifndef NDEBUG
+ size_t tx = _output_port->write (&d[0], d.size(), 0);
+ assert (tx == d.size());
+ return tx;
+#else
+ return _output_port->write (&d[0], d.size(), 0);
+#endif
+}
+
+/* ****************************************************************************
+ * MIDI Callbacks
+ */
+void
+FaderPort8::polypressure_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+ debug_2byte_msg ("PP", tb->controller_number, tb->value);
+ // outgoing only (meter)
+}
+
+void
+FaderPort8::pitchbend_handler (MIDI::Parser &, uint8_t chan, MIDI::pitchbend_t pb)
+{
+ debug_2byte_msg ("PB", chan, pb);
+ /* fader 0..16368 (0x3ff0 -- 1024 steps) */
+ _ctrls.midi_fader (chan, pb);
+}
+
+void
+FaderPort8::controller_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+ debug_2byte_msg ("CC", tb->controller_number, tb->value);
+ // encoder
+ // val Bit 7 = direction, Bits 0-6 = number of steps
+ if (tb->controller_number == 0x3c) {
+ encoder_navigate (tb->value & 0x40 ? true : false, tb->value & 0x3f);
+ }
+ if (tb->controller_number == 0x10) {
+ encoder_parameter (tb->value & 0x40 ? true : false, tb->value & 0x3f);
+ }
+}
+
+void
+FaderPort8::note_on_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+ debug_2byte_msg ("ON", tb->note_number, tb->velocity);
+
+ /* fader touch */
+ if (tb->note_number >= 0x68 && tb->note_number <= 0x6f) {
+ _ctrls.midi_touch (tb->note_number - 0x68, tb->velocity);
+ return;
+ }
+
+ /* special case shift */
+ if (tb->note_number == 0x06 || tb->note_number == 0x46) {
+ _shift_pressed = true;
+ _shift_connection.disconnect ();
+ if (_shift_lock) {
+ _shift_lock = false;
+ ShiftButtonChange (false);
+ tx_midi3 (0x90, 0x06, 0x00);
+ tx_midi3 (0x90, 0x46, 0x00);
+ return;
+ }
+
+ Glib::RefPtr<Glib::TimeoutSource> shift_timer =
+ Glib::TimeoutSource::create (1000);
+ shift_timer->attach (main_loop()->get_context());
+ _shift_connection = shift_timer->connect (sigc::mem_fun (*this, &FaderPort8::shift_timeout));
+
+ ShiftButtonChange (true);
+ tx_midi3 (0x90, 0x06, 0x7f);
+ tx_midi3 (0x90, 0x46, 0x7f);
+ return;
+ }
+
+ _ctrls.midi_event (tb->note_number, tb->velocity);
+}
+
+void
+FaderPort8::note_off_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+ debug_2byte_msg ("OF", tb->note_number, tb->velocity);
+
+ if (tb->note_number >= 0x68 && tb->note_number <= 0x6f) {
+ // fader touch
+ _ctrls.midi_touch (tb->note_number - 0x68, tb->velocity);
+ return;
+ }
+
+ /* special case shift */
+ if (tb->note_number == 0x06 || tb->note_number == 0x46) {
+ _shift_pressed = false;
+ if (_shift_lock) {
+ return;
+ }
+ ShiftButtonChange (false);
+ tx_midi3 (0x90, 0x06, 0x00);
+ tx_midi3 (0x90, 0x46, 0x00);
+ /* just in case this happens concurrently */
+ _shift_connection.disconnect ();
+ _shift_lock = false;
+ return;
+ }
+
+ bool handled = _ctrls.midi_event (tb->note_number, tb->velocity);
+ /* if Shift key is held while activating an action, don't lock shift. */
+ if (_shift_pressed && handled) {
+ _shift_connection.disconnect ();
+ _shift_lock = false;
+ }
+}
+
+void
+FaderPort8::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t size)
+{
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::FaderPort8)) {
+ DEBUG_STR_DECL(a);
+ DEBUG_STR_APPEND(a, string_compose ("RECV sysex siz=%1", size));
+ for (size_t i=0; i < size; ++i) {
+ DEBUG_STR_APPEND(a,hex);
+ DEBUG_STR_APPEND(a,"0x");
+ DEBUG_STR_APPEND(a,(int)buf[i]);
+ DEBUG_STR_APPEND(a,' ');
+ }
+ DEBUG_STR_APPEND(a,'\n');
+ DEBUG_TRACE (DEBUG::FaderPort8, DEBUG_STR(a).str());
+ }
+#endif
+}
+
+/* ****************************************************************************
+ * User actions
+ */
+void
+FaderPort8::set_button_action (FP8Controls::ButtonId id, bool press, std::string const& action_name)
+{
+ if (_ctrls.user_buttons().find (id) == _ctrls.user_buttons().end ()) {
+ return;
+ }
+ _user_action_map[id].action (press).assign_action (action_name);
+
+ if (!_device_active) {
+ return;
+ }
+ _ctrls.button (id).set_active (!_user_action_map[id].empty ());
+}
+
+std::string
+FaderPort8::get_button_action (FP8Controls::ButtonId id, bool press)
+{
+ return _user_action_map[id].action(press)._action_name;
+}
+
+/* ****************************************************************************
+ * Persistent State
+ */
+XMLNode&
+FaderPort8::get_state ()
+{
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::get_state\n");
+ XMLNode& node (ControlProtocol::get_state());
+
+ XMLNode* child;
+
+ child = new XMLNode (X_("Input"));
+ child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state());
+ node.add_child_nocopy (*child);
+
+ child = new XMLNode (X_("Output"));
+ child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state());
+ node.add_child_nocopy (*child);
+
+ for (UserActionMap::const_iterator i = _user_action_map.begin (); i != _user_action_map.end (); ++i) {
+ if (i->second.empty()) {
+ continue;
+ }
+ std::string name;
+ if (!_ctrls.button_enum_to_name (i->first, name)) {
+ continue;
+ }
+ XMLNode* btn = new XMLNode (X_("Button"));
+ btn->add_property (X_("id"), name);
+ if (!i->second.action(true).empty ()) {
+ btn->add_property ("press", i->second.action(true)._action_name);
+ }
+ if (!i->second.action(false).empty ()) {
+ btn->add_property ("release", i->second.action(false)._action_name);
+ }
+ node.add_child_nocopy (*btn);
+ }
+
+ return node;
+}
+
+int
+FaderPort8::set_state (const XMLNode& node, int version)
+{
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::set_state\n");
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ XMLNode const* child;
+
+ if (ControlProtocol::set_state (node, version)) {
+ return -1;
+ }
+
+ if ((child = node.child (X_("Input"))) != 0) {
+ XMLNode* portnode = child->child (Port::state_node_name.c_str());
+ if (portnode) {
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::set_state Input\n");
+ boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version);
+ }
+ }
+
+ if ((child = node.child (X_("Output"))) != 0) {
+ XMLNode* portnode = child->child (Port::state_node_name.c_str());
+ if (portnode) {
+ DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::set_state Output\n");
+ boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version);
+ }
+ }
+
+ _user_action_map.clear ();
+ // TODO: When re-loading state w/o surface re-init becomes possible,
+ // unset lights and reset colors of user buttons.
+
+ for (XMLNodeList::const_iterator n = node.children().begin(); n != node.children().end(); ++n) {
+ if ((*n)->name() != X_("Button")) {
+ continue;
+ }
+ XMLProperty const* prop = (*n)->property (X_("id"));
+ if (!prop) {
+ continue;
+ }
+
+ FP8Controls::ButtonId id;
+ if (!_ctrls.button_name_to_enum (prop->value(), id)) {
+ continue;
+ }
+
+ prop = (*n)->property (X_("press"));
+ if (prop) {
+ set_button_action (id, true, prop->value());
+ }
+ prop = (*n)->property (X_("release"));
+ if (prop) {
+ set_button_action (id, false, prop->value());
+ }
+ }
+
+ return 0;
+}
+
+/* ****************************************************************************
+ * Stripable Assignment
+ */
+
+static bool flt_audio_track (boost::shared_ptr<Stripable> s) {
+ return boost::dynamic_pointer_cast<AudioTrack>(s) != 0;
+}
+
+static bool flt_midi_track (boost::shared_ptr<Stripable> s) {
+ return boost::dynamic_pointer_cast<MidiTrack>(s) != 0;
+}
+
+static bool flt_bus (boost::shared_ptr<Stripable> s) {
+ if (boost::dynamic_pointer_cast<Route>(s) == 0) {
+ return false;
+ }
+#ifdef MIXBUS
+ if (s->mixbus () == 0) {
+ return false;
+ }
+#endif
+ return boost::dynamic_pointer_cast<Track>(s) == 0;
+}
+
+static bool flt_auxbus (boost::shared_ptr<Stripable> s) {
+ if (boost::dynamic_pointer_cast<Route>(s) == 0) {
+ return false;
+ }
+#ifdef MIXBUS
+ if (s->mixbus () > 0) {
+ return false;
+ }
+#endif
+ return boost::dynamic_pointer_cast<Track>(s) == 0;
+}
+
+static bool flt_vca (boost::shared_ptr<Stripable> s) {
+ return boost::dynamic_pointer_cast<VCA>(s) != 0;
+}
+
+static bool flt_selected (boost::shared_ptr<Stripable> s) {
+ return s->is_selected ();
+}
+
+static bool flt_mains (boost::shared_ptr<Stripable> s) {
+ return (s->is_master() || s->is_monitor());
+}
+
+static bool flt_all (boost::shared_ptr<Stripable> s) {
+ return true;
+}
+
+static bool flt_rec_armed (boost::shared_ptr<Stripable> s) {
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
+ if (!t) {
+ return false;
+ }
+ return t->rec_enable_control ()->get_value () > 0.;
+}
+
+static bool flt_instrument (boost::shared_ptr<Stripable> s) {
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route>(s);
+ if (!r) {
+ return false;
+ }
+ return 0 != r->the_instrument ();
+}
+
+struct FP8SortByNewDisplayOrder
+{
+ // return (a < b)
+ bool operator () (const boost::shared_ptr<Stripable> & a, const boost::shared_ptr<Stripable> & b) const
+ {
+ if (a->presentation_info().flags () == b->presentation_info().flags ()) {
+ return a->presentation_info().order() < b->presentation_info().order();
+ }
+
+ int cmp_a = 0;
+ int cmp_b = 0;
+
+ if (a->presentation_info().flags () & ARDOUR::PresentationInfo::VCA) {
+ cmp_a = 1;
+ }
+#ifdef MIXBUS
+ else if (a->presentation_info().flags () & ARDOUR::PresentationInfo::MasterOut) {
+ cmp_a = 3;
+ }
+ else if (a->presentation_info().flags () & ARDOUR::PresentationInfo::Mixbus || a->mixbus()) {
+ cmp_a = 2;
+ }
+#endif
+
+ if (b->presentation_info().flags () & ARDOUR::PresentationInfo::VCA) {
+ cmp_b = 1;
+ }
+#ifdef MIXBUS
+ else if (b->presentation_info().flags () & ARDOUR::PresentationInfo::MasterOut) {
+ cmp_b = 3;
+ }
+ else if (b->presentation_info().flags () & ARDOUR::PresentationInfo::Mixbus || b->mixbus()) {
+ cmp_b = 2;
+ }
+#endif
+
+#ifdef MIXBUS
+ // this can happen with older MB sessions (no PresentationInfo::Mixbus flag)
+ if (cmp_a == cmp_a) {
+ return a->presentation_info().order() < b->presentation_info().order();
+ }
+#endif
+ return cmp_a < cmp_b;
+ }
+};
+
+void
+FaderPort8::filter_stripables (StripableList& strips) const
+{
+ typedef bool (*FilterFunction)(boost::shared_ptr<Stripable>);
+ FilterFunction flt;
+
+ bool allow_master = false;
+ bool allow_monitor = false;
+
+ switch (_ctrls.mix_mode ()) {
+ case MixAudio:
+ flt = &flt_audio_track;
+ break;
+ case MixInstrument:
+ flt = &flt_instrument;
+ break;
+ case MixBus:
+ flt = &flt_bus;
+ break;
+ case MixVCA:
+ flt = &flt_vca;
+ break;
+ case MixMIDI:
+ flt = &flt_midi_track;
+ break;
+ case MixUser:
+ allow_master = true;
+ flt = &flt_selected;
+ break;
+ case MixOutputs:
+ allow_master = true;
+ allow_monitor = true;
+ flt = &flt_mains;
+ break;
+ case MixInputs:
+ flt = &flt_rec_armed;
+ break;
+ case MixFX:
+ flt = &flt_auxbus;
+ break;
+ case MixAll:
+ allow_master = true;
+ flt = &flt_all;
+ break;
+ }
+
+ StripableList all;
+ session->get_stripables (all);
+
+ for (StripableList::const_iterator s = all.begin(); s != all.end(); ++s) {
+ if ((*s)->is_auditioner ()) { continue; }
+ if ((*s)->is_hidden ()) { continue; }
+
+ if (!allow_master && (*s)->is_master ()) { continue; }
+ if (!allow_monitor && (*s)->is_monitor ()) { continue; }
+
+ if ((*flt)(*s)) {
+ strips.push_back (*s);
+ }
+ }
+ strips.sort (FP8SortByNewDisplayOrder());
+}
+
+/* Track/Pan mode: assign stripable to strips */
+void
+FaderPort8::assign_stripables ()
+{
+ StripableList strips;
+ filter_stripables (strips);
+
+ set_periodic_display_mode (FP8Strip::Stripables);
+
+ int n_strips = strips.size();
+ _channel_off = std::min (_channel_off, n_strips - 8);
+ _channel_off = std::max (0, _channel_off);
+
+ uint8_t id = 0;
+ int skip = _channel_off;
+ for (StripableList::const_iterator s = strips.begin(); s != strips.end(); ++s) {
+ if (skip > 0) {
+ --skip;
+ continue;
+ }
+
+ _assigned_strips[*s] = id;
+ (*s)->DropReferences.connect (assigned_stripable_connections, MISSING_INVALIDATOR,
+ boost::bind (&FaderPort8::notify_stripable_added_or_removed, this), this);
+
+ (*s)->PropertyChanged.connect (assigned_stripable_connections, MISSING_INVALIDATOR,
+ boost::bind (&FaderPort8::notify_stripable_property_changed, this, boost::weak_ptr<Stripable> (*s), _1), this);
+ (*s)->presentation_info ().PropertyChanged.connect (assigned_stripable_connections, MISSING_INVALIDATOR,
+ boost::bind (&FaderPort8::notify_stripable_property_changed, this, boost::weak_ptr<Stripable> (*s), _1), this);
+
+ _ctrls.strip(id).set_stripable (*s, _ctrls.fader_mode() == ModePan);
+
+ boost::function<void ()> cb (boost::bind (&FaderPort8::select_strip, this, boost::weak_ptr<Stripable> (*s)));
+ _ctrls.strip(id).set_select_cb (cb);
+
+ if (++id == 8) {
+ break;
+ }
+ }
+ for (; id < 8; ++id) {
+ _ctrls.strip(id).unset_controllables();
+ }
+}
+
+void
+FaderPort8::assign_processor_ctrls ()
+{
+ int n_parameters = _proc_params.size();
+ if (n_parameters == 0) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+ set_periodic_display_mode (FP8Strip::PluginParam);
+
+ _parameter_off = std::min (_parameter_off, n_parameters - 8);
+ _parameter_off = std::max (0, _parameter_off);
+
+ int skip = _parameter_off;
+ uint8_t id = 0;
+ for ( std::list <ProcessorCtrl>::iterator i = _proc_params.begin(); i != _proc_params.end(); ++i) {
+ if (skip > 0) {
+ --skip;
+ continue;
+ }
+ _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT1);
+ _ctrls.strip(id).set_fader_controllable ((*i).ac);
+ _ctrls.strip(id).set_text_line (0, (*i).name);
+
+ if (++id == 8) {
+ break;
+ }
+ }
+ // clear remaining
+ for (; id < 8; ++id) {
+ _ctrls.strip(id).unset_controllables ();
+ }
+}
+
+void
+FaderPort8::build_well_known_processor_ctrls (boost::shared_ptr<Stripable> s, bool eq)
+{
+#define PUSH_BACK_NON_NULL(N, C) do {if (C) { _proc_params.push_back (ProcessorCtrl (N, C)); }} while (0)
+
+ _proc_params.clear ();
+ if (eq) {
+ int cnt = s->eq_band_cnt();
+ PUSH_BACK_NON_NULL ("Enable", s->eq_enable_controllable ());
+ PUSH_BACK_NON_NULL ("HPF", s->eq_hpf_controllable ());
+ for (int band = 0; band < cnt; ++band) {
+ std::string bn = s->eq_band_name (band);
+ PUSH_BACK_NON_NULL (string_compose ("Gain %1", bn), s->eq_gain_controllable (band));
+ PUSH_BACK_NON_NULL (string_compose ("Freq %1", bn), s->eq_freq_controllable (band));
+ PUSH_BACK_NON_NULL (string_compose ("Band %1", bn), s->eq_q_controllable (band));
+ PUSH_BACK_NON_NULL (string_compose ("Shape %1", bn), s->eq_shape_controllable (band));
+ }
+ } else {
+ PUSH_BACK_NON_NULL ("Enable", s->comp_enable_controllable ());
+ PUSH_BACK_NON_NULL ("Threshold", s->comp_threshold_controllable ());
+ PUSH_BACK_NON_NULL ("Speed", s->comp_speed_controllable ());
+ PUSH_BACK_NON_NULL ("Mode", s->comp_mode_controllable ());
+ }
+}
+
+void
+FaderPort8::select_plugin (int num)
+{
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (first_selected_stripable());
+ if (!r) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+ if (num < 0) {
+ build_well_known_processor_ctrls (r, num == -1);
+ assign_processor_ctrls ();
+ return;
+ }
+
+ boost::shared_ptr<Processor> proc = r->nth_plugin (num);
+ if (!proc) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ // switching to "Mode Track" -> calls FaderPort8::notify_fader_mode_changed()
+ // which drops the references, disconnects the signal and re-spills tracks
+ proc->DropReferences.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FP8Controls::set_fader_mode, &_ctrls, ModeTrack), this);
+
+ // build params
+ _proc_params.clear();
+ set<Evoral::Parameter> p = proc->what_can_be_automated ();
+ for (set<Evoral::Parameter>::iterator i = p.begin(); i != p.end(); ++i) {
+ std::string n = proc->describe_parameter (*i);
+ if (n == "hidden") {
+ continue;
+ }
+ _proc_params.push_back (ProcessorCtrl (n, proc->automation_control (*i)));
+ }
+
+ // display
+ assign_processor_ctrls ();
+}
+
+/* short 4 chars at most */
+static std::string plugintype (ARDOUR::PluginType t) {
+ switch (t) {
+ case AudioUnit:
+ return "AU";
+ case LADSPA:
+ return "LV1";
+ case LV2:
+ return "LV2";
+ case Windows_VST:
+ case LXVST:
+ case MacVST:
+ return "VST";
+ case Lua:
+ return "Lua";
+ default:
+ break;
+ }
+ return enum_2_string (t);
+}
+
+void
+FaderPort8::spill_plugins ()
+{
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (first_selected_stripable());
+ if (!r) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ drop_ctrl_connections ();
+
+ // switching to "Mode Track" -> calls FaderPort8::notify_fader_mode_changed()
+ // which drops the references, disconnects the signal and re-spills tracks
+ r->DropReferences.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FP8Controls::set_fader_mode, &_ctrls, ModeTrack), this);
+
+ // update when processor change
+ r->processors_changed.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::spill_plugins, this), this);
+
+ // count available
+ boost::shared_ptr<Processor> proc;
+
+ std::vector<uint32_t> procs;
+
+ for (uint32_t i = 0; 0 != (proc = r->nth_plugin (i)); ++i) {
+ if (!proc->display_to_user ()) {
+ continue;
+ }
+ int n_controls = 0;
+ set<Evoral::Parameter> p = proc->what_can_be_automated ();
+ for (set<Evoral::Parameter>::iterator i = p.begin(); i != p.end(); ++i) {
+ std::string n = proc->describe_parameter (*i);
+ if (n == "hidden") {
+ continue;
+ }
+ ++n_controls;
+ }
+ if (n_controls > 0) {
+ procs.push_back (i);
+ }
+ }
+
+ int n_plugins = procs.size();
+ int spillwidth = 8;
+ bool have_well_known_eq = false;
+ bool have_well_known_comp = false;
+
+ // reserve last slot(s) for "well-known"
+ if (r->eq_band_cnt() > 0) {
+ --spillwidth;
+ have_well_known_eq = true;
+ }
+ if (r->comp_enable_controllable ()) {
+ --spillwidth;
+ have_well_known_comp = true;
+ }
+
+ if (n_plugins == 0 && !have_well_known_eq && !have_well_known_comp) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ set_periodic_display_mode (FP8Strip::PluginSelect);
+
+ _plugin_off = std::min (_plugin_off, n_plugins - spillwidth);
+ _plugin_off = std::max (0, _plugin_off);
+
+ uint8_t id = 0;
+ for (uint32_t i = _plugin_off; ; ++i) {
+ if (i >= procs.size()) {
+ break;
+ }
+ boost::shared_ptr<Processor> proc = r->nth_plugin (procs[i]);
+ if (!proc) {
+ break;
+ }
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
+ boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin, this, i));
+
+ _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT & ~FP8Strip::CTRL_SELECT);
+ _ctrls.strip(id).set_select_cb (cb);
+ _ctrls.strip(id).select_button ().set_color (0x00ff00ff);
+ _ctrls.strip(id).select_button ().set_active (true /*proc->enabled()*/);
+ _ctrls.strip(id).select_button ().set_blinking (false);
+ _ctrls.strip(id).set_text_line (0, proc->name());
+ _ctrls.strip(id).set_text_line (1, pi->plugin()->maker());
+ _ctrls.strip(id).set_text_line (2, plugintype (pi->type()));
+
+ if (++id == spillwidth) {
+ break;
+ }
+ }
+ // clear remaining
+ for (; id < spillwidth; ++id) {
+ _ctrls.strip(id).unset_controllables ();
+ }
+
+ if (have_well_known_comp) {
+ assert (id < 8);
+ boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin, this, -2));
+ _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT & ~FP8Strip::CTRL_SELECT);
+ _ctrls.strip(id).set_select_cb (cb);
+ _ctrls.strip(id).select_button ().set_color (0xffff00ff);
+ _ctrls.strip(id).select_button ().set_active (true);
+ _ctrls.strip(id).select_button ().set_blinking (false);
+ _ctrls.strip(id).set_text_line (0, "Comp");
+ _ctrls.strip(id).set_text_line (1, "Built-In");
+ _ctrls.strip(id).set_text_line (2, "--");
+ ++id;
+ }
+ if (have_well_known_eq) {
+ assert (id < 8);
+ boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin, this, -1));
+ _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT & ~FP8Strip::CTRL_SELECT);
+ _ctrls.strip(id).set_select_cb (cb);
+ _ctrls.strip(id).select_button ().set_color (0xffff00ff);
+ _ctrls.strip(id).select_button ().set_active (true);
+ _ctrls.strip(id).select_button ().set_blinking (false);
+ _ctrls.strip(id).set_text_line (0, "EQ");
+ _ctrls.strip(id).set_text_line (1, "Built-In");
+ _ctrls.strip(id).set_text_line (2, "--");
+ ++id;
+ }
+ assert (id == 8);
+}
+
+void
+FaderPort8::assign_sends ()
+{
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ if (!s) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ int n_sends = 0;
+ while (0 != s->send_level_controllable (n_sends)) {
+ ++n_sends;
+ }
+ if (n_sends == 0) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ drop_ctrl_connections ();
+ s->DropReferences.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FP8Controls::set_fader_mode, &_ctrls, ModeTrack), this);
+
+ set_periodic_display_mode (FP8Strip::PluginParam);
+
+ _plugin_off = std::min (_plugin_off, n_sends - 8);
+ _plugin_off = std::max (0, _plugin_off);
+
+ uint8_t id = 0;
+ int skip = _parameter_off;
+ for (uint32_t i = _plugin_off; ; ++i) {
+ if (skip > 0) {
+ --skip;
+ continue;
+ }
+ boost::shared_ptr<AutomationControl> send = s->send_level_controllable (i);
+ if (!send) {
+ break;
+ }
+
+ _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT1);
+ _ctrls.strip(id).set_fader_controllable (send);
+ _ctrls.strip(id).set_text_line (0, s->send_name (i));
+ //_ctrls.strip(id).set_mute_controllable (s->send_enable_controllable (i)); // XXX TODO MB assign -> select ?
+
+ if (++id == 8) {
+ break;
+ }
+ }
+ // clear remaining
+ for (; id < 8; ++id) {
+ _ctrls.strip(id).unset_controllables ();
+ }
+}
+
+void
+FaderPort8::set_periodic_display_mode (FP8Strip::DisplayMode m)
+{
+ for (uint8_t id = 0; id < 8; ++id) {
+ _ctrls.strip(id).set_periodic_display_mode (m);
+ }
+}
+
+void
+FaderPort8::assign_strips (bool reset_bank)
+{
+ if (reset_bank) {
+ _channel_off = 0;
+ }
+
+ _assigned_strips.clear ();
+ assigned_stripable_connections.drop_connections ();
+
+ FaderMode fadermode = _ctrls.fader_mode ();
+ switch (fadermode) {
+ case ModeTrack:
+ case ModePan:
+ assign_stripables ();
+ gui_track_selection_changed (); // update selection, automation-state
+ break;
+ case ModePlugins:
+ if (_proc_params.size() > 0) {
+ assign_processor_ctrls ();
+ } else {
+ spill_plugins ();
+ }
+ break;
+ case ModeSend:
+ assign_sends ();
+ break;
+ }
+}
+
+
+void
+FaderPort8::drop_ctrl_connections ()
+{
+ _proc_params.clear();
+ processor_connections.drop_connections ();
+}
+
+void
+FaderPort8::notify_fader_mode_changed ()
+{
+ FaderMode fadermode = _ctrls.fader_mode ();
+
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ if (!s && (fadermode == ModePlugins || fadermode == ModeSend)) {
+ _ctrls.set_fader_mode (ModeTrack);
+ return;
+ }
+
+ drop_ctrl_connections ();
+
+ switch (fadermode) {
+ case ModeTrack:
+ case ModePan:
+ break;
+ case ModePlugins:
+ case ModeSend:
+ _plugin_off = 0;
+ _parameter_off = 0;
+ // force unset rec-arm button, see also FaderPort8::button_arm
+ _ctrls.button (FP8Controls::BtnArm).set_active (false);
+ ARMButtonChange (false);
+ break;
+ }
+ assign_strips (false);
+ notify_automation_mode_changed ();
+}
+
+/* ****************************************************************************
+ * Assigned Stripable Callbacks
+ */
+
+void
+FaderPort8::notify_stripable_added_or_removed ()
+{
+ /* called by
+ * - DropReferences
+ * - session->RouteAdded
+ * - PresentationInfo::Change -> Properties::hidden
+ */
+ assign_strips (false);
+}
+
+/* functor for FP8Strip's select button */
+void
+FaderPort8::select_strip (boost::weak_ptr<Stripable> ws)
+{
+ boost::shared_ptr<Stripable> s = ws.lock();
+ if (!s) {
+ return;
+ }
+ if (_shift_pressed) {
+ if (s->is_selected ()) {
+ RemoveStripableFromSelection (s);
+ } else {
+ SetStripableSelection (s);
+ }
+ return;
+ }
+ if (s->is_selected () && s != first_selected_stripable ()) {
+ set_first_selected_stripable (s);
+ gui_track_selection_changed ();
+ } else {
+ ToggleStripableSelection (s);
+ }
+}
+
+/* called from static PresentationInfo::Change */
+void
+FaderPort8::notify_pi_property_changed (const PropertyChange& what_changed)
+{
+ if (what_changed.contains (Properties::hidden)) {
+ notify_stripable_added_or_removed ();
+ }
+ if (what_changed.contains (Properties::order)) {
+ notify_stripable_added_or_removed ();
+ }
+ // Properties::selected is handled via StripableSelectionChanged
+}
+
+void
+FaderPort8::notify_stripable_property_changed (boost::weak_ptr<Stripable> ws, const PropertyChange& what_changed)
+{
+ boost::shared_ptr<Stripable> s = ws.lock();
+ if (!s) {
+ assert (0); // this should not happen
+ return;
+ }
+ assert (_assigned_strips.find (s) != _assigned_strips.end());
+ uint8_t id = _assigned_strips[s];
+
+ if (what_changed.contains (Properties::color)) {
+ _ctrls.strip(id).select_button ().set_color (s->presentation_info ().color());
+ }
+
+ if (what_changed.contains (Properties::name)) {
+ _ctrls.strip(id).set_text_line (0, s->name());
+ }
+}
+
+void
+FaderPort8::gui_track_selection_changed (/*ARDOUR::StripableNotificationListPtr*/)
+{
+ automation_state_connections.drop_connections();
+
+ switch (_ctrls.fader_mode ()) {
+ case ModePlugins:
+ if (_proc_params.size () > 0) {
+ ; // TODO w/"well-known" -> re-assign to new strip ?!
+ } else {
+ notify_fader_mode_changed ();
+ }
+ return;
+ case ModeSend:
+ notify_automation_mode_changed ();
+ return;
+ default:
+ break;
+ }
+
+ for (StripAssignmentMap::const_iterator i = _assigned_strips.begin(); i != _assigned_strips.end(); ++i) {
+ boost::shared_ptr<ARDOUR::Stripable> s = i->first;
+ uint8_t id = i->second;
+ bool sel = s->is_selected ();
+ _ctrls.strip(id).select_button ().set_active (sel);
+ _ctrls.strip(id).select_button ().set_blinking (sel && s == first_selected_stripable ());
+ }
+
+ boost::shared_ptr<Stripable> s = first_selected_stripable();
+ if (s) {
+ boost::shared_ptr<AutomationControl> ac;
+ ac = s->gain_control();
+ if (ac && ac->alist()) {
+ ac->alist()->automation_state_changed.connect (automation_state_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_automation_mode_changed, this), this);
+ }
+ ac = s->pan_azimuth_control();
+ if (ac && ac->alist()) {
+ ac->alist()->automation_state_changed.connect (automation_state_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_automation_mode_changed, this), this);
+ }
+ }
+ notify_automation_mode_changed ();
+}
+
+
+/* ****************************************************************************
+ * Banking
+ */
+
+void
+FaderPort8::move_selected_into_view ()
+{
+ boost::shared_ptr<Stripable> selected = first_selected_stripable ();
+ if (!selected) {
+ return;
+ }
+
+ StripableList strips;
+ filter_stripables (strips);
+
+ StripableList::iterator it = std::find (strips.begin(), strips.end(), selected);
+ if (it == strips.end()) {
+ return;
+ }
+ int off = std::distance (strips.begin(), it);
+
+ if (_channel_off <= off && off < _channel_off + 8) {
+ return;
+ }
+
+ if (_channel_off > off) {
+ _channel_off = off;
+ } else {
+ _channel_off = off - 7;
+ }
+ assign_strips (false);
+}
+
+void
+FaderPort8::bank (bool down, bool page)
+{
+ int dt = page ? 8 : 1;
+ if (down) {
+ dt *= -1;
+ }
+ _channel_off += dt;
+ assign_strips (false);
+}
+
+void
+FaderPort8::bank_param (bool down, bool page)
+{
+ int dt = page ? 8 : 1;
+ if (down) {
+ dt *= -1;
+ }
+ _channel_off += dt;
+ switch (_ctrls.fader_mode ()) {
+ case ModePlugins:
+ if (_proc_params.size() > 0) {
+ _parameter_off += dt;
+ assign_processor_ctrls ();
+ } else {
+ _plugin_off += dt;
+ spill_plugins ();
+ }
+ break;
+ default:
+ break;
+ }
+}
diff --git a/libs/surfaces/faderport8/faderport8.h b/libs/surfaces/faderport8/faderport8.h
new file mode 100644
index 0000000000..64b5b4decc
--- /dev/null
+++ b/libs/surfaces/faderport8/faderport8.h
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef ardour_surface_faderport8_h
+#define ardour_surface_faderport8_h
+
+#include <list>
+#include <map>
+#include <glibmm/threads.h>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+#include "pbd/properties.h"
+
+#include "ardour/types.h"
+#include "ardour/async_midi_port.h"
+#include "ardour/midi_port.h"
+
+#include "control_protocol/control_protocol.h"
+
+#include "fp8_base.h"
+#include "fp8_controls.h"
+
+namespace MIDI {
+ class Parser;
+}
+
+namespace ARDOUR {
+ class Bundle;
+ class Session;
+ class Processor;
+}
+
+namespace ArdourSurface {
+
+struct FaderPort8Request : public BaseUI::BaseRequestObject
+{
+ public:
+ FaderPort8Request () {}
+ ~FaderPort8Request () {}
+};
+
+class FaderPort8 : public FP8Base, public ARDOUR::ControlProtocol, public AbstractUI<FaderPort8Request>
+{
+public:
+ FaderPort8 (ARDOUR::Session&);
+ virtual ~FaderPort8();
+
+ int set_active (bool yn);
+
+ /* we probe for a device when our ports are connected. Before that,
+ * there's no way to know if the device exists or not.
+ */
+ static bool probe() { return true; }
+ static void* request_factory (uint32_t);
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ /* configuration GUI */
+ bool has_editor () const { return true; }
+ void* get_gui () const;
+ void tear_down_gui ();
+ PBD::Signal0<void> ConnectionChange;
+
+ void set_button_action (FP8Controls::ButtonId, bool, std::string const&);
+ std::string get_button_action (FP8Controls::ButtonId, bool);
+ FP8Controls const& control () const { return _ctrls; }
+
+ int stop ();
+ void do_request (FaderPort8Request*);
+ void thread_init ();
+
+ boost::shared_ptr<ARDOUR::Port> input_port() const { return _input_port; }
+ boost::shared_ptr<ARDOUR::Port> output_port() const { return _output_port; }
+ std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
+
+ size_t tx_midi (std::vector<uint8_t> const&) const;
+
+private:
+ void close ();
+
+ void start_midi_handling ();
+ void stop_midi_handling ();
+
+ /* I/O Ports */
+ PBD::ScopedConnection port_connection;
+ boost::shared_ptr<ARDOUR::AsyncMIDIPort> _input_port;
+ boost::shared_ptr<ARDOUR::AsyncMIDIPort> _output_port;
+ boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
+ boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
+
+ bool midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::AsyncMIDIPort> port);
+
+ bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
+
+ enum ConnectionState {
+ InputConnected = 0x1,
+ OutputConnected = 0x2
+ };
+
+ void connected ();
+ void disconnected ();
+ int _connection_state;
+ bool _device_active;
+
+ /* MIDI input message handling */
+ void sysex_handler (MIDI::Parser &p, MIDI::byte *, size_t);
+ void polypressure_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ void pitchbend_handler (MIDI::Parser &, uint8_t chan, MIDI::pitchbend_t pb);
+ void controller_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ void note_on_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ void note_off_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ PBD::ScopedConnectionList midi_connections;
+
+ /* ***************************************************************************
+ * Control Elements
+ */
+ FP8Controls _ctrls;
+ void notify_stripable_added_or_removed ();
+ void notify_fader_mode_changed ();
+ void filter_stripables (ARDOUR::StripableList& strips) const;
+ void assign_stripables ();
+ void set_periodic_display_mode (FP8Strip::DisplayMode);
+
+ void assign_strips (bool reset_bank);
+ void bank (bool down, bool page);
+ void move_selected_into_view ();
+
+ void assign_sends ();
+ void spill_plugins ();
+ void assign_processor_ctrls ();
+ void build_well_known_processor_ctrls (boost::shared_ptr<ARDOUR::Stripable>, bool);
+ void select_plugin (int num);
+
+ void bank_param (bool down, bool page);
+ /* bank offsets */
+ int _channel_off;
+ int _plugin_off;
+ int _parameter_off;
+
+ /* plugin + send mode stripable
+ *
+ * This is used when parameters of one strip are assigned to
+ * individual FP8Strip controls (Edit Send, Edit Plugins).
+ *
+ * When there's one stripable per FP8Strip, FP8Strip itself keeps
+ * track of the object lifetime and these are NULL.
+ */
+ PBD::ScopedConnectionList processor_connections;
+
+ PBD::ScopedConnectionList assigned_stripable_connections;
+ typedef std::map<boost::shared_ptr<ARDOUR::Stripable>, uint8_t> StripAssignmentMap;
+ StripAssignmentMap _assigned_strips;
+
+ void drop_ctrl_connections ();
+
+ void select_strip (boost::weak_ptr<ARDOUR::Stripable>);
+ void notify_pi_property_changed (const PBD::PropertyChange&);
+ void notify_stripable_property_changed (boost::weak_ptr<ARDOUR::Stripable>, const PBD::PropertyChange&);
+ void gui_track_selection_changed ();
+
+ PBD::ScopedConnection selection_connection;
+ PBD::ScopedConnectionList automation_state_connections;
+ PBD::ScopedConnectionList modechange_connections;
+ /* **************************************************************************/
+ struct ProcessorCtrl {
+ ProcessorCtrl (std::string const &n, boost::shared_ptr<ARDOUR::AutomationControl> c)
+ : name (n)
+ , ac (c)
+ {}
+ std::string name;
+ boost::shared_ptr<ARDOUR::AutomationControl> ac;
+ };
+ std::list <ProcessorCtrl> _proc_params;
+ /* **************************************************************************/
+
+ /* periodic updates, parameter poll */
+ sigc::connection _periodic_connection;
+ bool periodic ();
+ std::string _timecode;
+ std::string const& timecode () const { return _timecode; }
+
+ /* sync button blink -- the FP's blink mode does not work */
+ sigc::connection _blink_connection;
+ bool _blink_onoff;
+ bool blink_it ();
+
+ /* shift key */
+ sigc::connection _shift_connection;
+ bool _shift_lock;
+ bool _shift_pressed;
+ bool shift_timeout () { _shift_lock = true; return false; }
+ bool shift_mod () const { return _shift_lock | _shift_pressed; }
+
+ /* GUI */
+ void build_gui ();
+ mutable void *gui;
+
+ /* setup callbacks & actions */
+ void connect_session_signals ();
+ void setup_actions ();
+ void send_session_state ();
+
+ /* callbacks */
+ PBD::ScopedConnectionList session_connections;
+ void notify_parameter_changed (std::string);
+ void notify_record_state_changed ();
+ void notify_transport_state_changed ();
+ void notify_loop_state_changed ();
+ void notify_snap_change ();
+ void notify_session_dirty_changed ();
+ void notify_history_changed ();
+ void notify_solo_changed ();
+ void notify_mute_changed ();
+ void notify_automation_mode_changed ();
+
+ /* actions */
+ PBD::ScopedConnectionList button_connections;
+ void button_play ();
+ void button_stop ();
+ void button_record ();
+ void button_loop ();
+ void button_metronom ();
+ void button_varispeed (bool);
+ void button_mute_clear ();
+ void button_arm (bool);
+ void button_arm_all ();
+ void button_automation (ARDOUR::AutoState);
+ void button_prev_next (bool);
+ void button_action (const std::string& group, const std::string& item);
+
+ void button_encoder ();
+ void button_parameter ();
+ void encoder_navigate (bool, int);
+ void encoder_parameter (bool, int);
+
+ /* user bound actions */
+ void button_user (bool, FP8Controls::ButtonId);
+
+ enum ActionType {
+ Unset,
+ NamedAction,
+ // InternalFunction, // unused
+ };
+
+ struct UserAction {
+ UserAction () : _type (Unset) {}
+
+ ActionType _type;
+ std::string _action_name;
+ //boost::function<void()> function; // unused
+
+ void clear ()
+ {
+ _type = Unset;
+ _action_name.clear();
+ }
+
+ void assign_action (std::string const& action_name)
+ {
+ if (action_name.empty ()) {
+ _type = Unset;
+ _action_name.clear();
+ } else {
+ _type = NamedAction;
+ _action_name = action_name;
+ }
+ }
+
+ bool empty () const
+ {
+ return _type == Unset;
+ }
+
+ void call (FaderPort8& _base) const
+ {
+ switch (_type) {
+ case NamedAction:
+ _base.access_action (_action_name);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ struct ButtonAction {
+ UserAction on_press;
+ UserAction on_release;
+
+ UserAction& action (bool press)
+ {
+ return press ? on_press : on_release;
+ }
+
+ UserAction const& action (bool press) const
+ {
+ return press ? on_press : on_release;
+ }
+
+ void call (FaderPort8& _base, bool press) const
+ {
+ action (press).call (_base);
+ }
+ bool empty () const
+ {
+ return on_press.empty () && on_release.empty();
+ }
+ };
+
+ typedef std::map<FP8Controls::ButtonId, ButtonAction> UserActionMap;
+ UserActionMap _user_action_map;
+};
+
+} /* namespace */
+
+#endif /* ardour_surface_faderport8_h */
diff --git a/libs/surfaces/faderport8/faderport8_interface.cc b/libs/surfaces/faderport8/faderport8_interface.cc
new file mode 100644
index 0000000000..c87751cf73
--- /dev/null
+++ b/libs/surfaces/faderport8/faderport8_interface.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <pbd/failed_constructor.h>
+
+#include "control_protocol/control_protocol.h"
+#include "faderport8.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, Session* s)
+{
+ FaderPort8* fp;
+
+ try {
+ fp = new FaderPort8 (*s);
+ } catch (failed_constructor& err) {
+ return 0;
+ }
+
+ if (fp->set_active (true)) {
+ delete fp;
+ return 0;
+ }
+
+ return fp;
+}
+
+static void
+delete_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, ControlProtocol* cp)
+{
+ delete cp;
+}
+
+static bool
+probe_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/)
+{
+ return FaderPort8::probe ();
+}
+
+static void*
+faderport8_request_buffer_factory (uint32_t num_requests)
+{
+ return FaderPort8::request_factory (num_requests);
+}
+
+static ControlProtocolDescriptor faderport8_midi_descriptor = {
+ /*name : */ "PreSonus FaderPort8",
+ /*id : */ "uri://ardour.org/surfaces/faderport8:0",
+ /*ptr : */ 0,
+ /*module : */ 0,
+ /*mandatory : */ 0,
+ /*supports_feedback : */ true,
+ /*probe : */ probe_faderport8_midi_protocol,
+ /*initialize : */ new_faderport8_midi_protocol,
+ /*destroy : */ delete_faderport8_midi_protocol,
+ /*request_buffer_factory */ faderport8_request_buffer_factory
+};
+
+extern "C" ARDOURSURFACE_API
+ControlProtocolDescriptor* protocol_descriptor () {
+ return &faderport8_midi_descriptor;
+}
diff --git a/libs/surfaces/faderport8/fp8_base.h b/libs/surfaces/faderport8/fp8_base.h
new file mode 100644
index 0000000000..b0d5df4923
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_base.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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_surfaces_fp8base_h_
+#define _ardour_surfaces_fp8base_h_
+
+#include <stdint.h>
+#include <vector>
+
+#include "pbd/signals.h"
+
+namespace ArdourSurface {
+
+/* conveniece wrappers depending on "FP8Base& _base" */
+#define fp8_loop dynamic_cast<BaseUI*>(&_base)->main_loop
+#define fp8_context() dynamic_cast<BaseUI*>(&_base)
+#define fp8_protocol() dynamic_cast<ControlProtocol*>(&_base)
+
+class FP8Base
+{
+public:
+ virtual ~FP8Base() {}
+
+ virtual size_t tx_midi (std::vector<uint8_t> const&) const = 0;
+ virtual std::string const& timecode () const = 0;
+
+ size_t tx_midi2 (uint8_t sb, uint8_t d1) const
+ {
+ std::vector<uint8_t> d;
+ d.push_back (sb);
+ d.push_back (d1);
+ return tx_midi (d);
+ }
+
+ size_t tx_midi3 (uint8_t sb, uint8_t d1, uint8_t d2) const
+ {
+ std::vector<uint8_t> d;
+ d.push_back (sb);
+ d.push_back (d1);
+ d.push_back (d2);
+ return tx_midi (d);
+ }
+
+ size_t tx_sysex (size_t count, ...)
+ {
+ std::vector<uint8_t> d;
+ sysexhdr (d);
+
+ va_list var_args;
+ va_start (var_args, count);
+ for (size_t i = 0; i < count; ++i)
+ {
+ // uint8_t {aka unsigned char} is promoted to ‘int’ when passed through ‘...’
+ uint8_t b = va_arg (var_args, int);
+ d.push_back (b);
+ }
+ va_end (var_args);
+
+ d.push_back (0xf7);
+ return tx_midi (d);
+ }
+
+ size_t tx_text (uint8_t id, uint8_t line, uint8_t align, std::string const& txt)
+ {
+ std::vector<uint8_t> d;
+ sysexhdr (d);
+ d.push_back (0x12);
+ d.push_back (id & 0x07);
+ d.push_back (line & 0x03);
+ d.push_back (align & 0x07);
+
+ for (size_t i = 0; i < txt.size(); ++i)
+ {
+ d.push_back (txt[i]);
+ if (i >= 8) {
+ break;
+ }
+ }
+ d.push_back (0xf7);
+ return tx_midi (d);
+ }
+
+ PBD::Signal1<void, bool> ShiftButtonChange;
+ PBD::Signal1<void, bool> ARMButtonChange;
+
+ PBD::Signal1<void, bool> BlinkIt;
+ PBD::Signal0<void> Periodic;
+
+private:
+ void sysexhdr (std::vector<uint8_t>& d)
+ {
+ /* faderport8 <SysExHdr> */
+ d.push_back (0xf0);
+ d.push_back (0x00);
+ d.push_back (0x01);
+ d.push_back (0x06);
+ d.push_back (0x02);
+ }
+};
+
+namespace FP8Types {
+
+ enum FaderMode {
+ ModeTrack,
+ ModePlugins,
+ ModeSend,
+ ModePan
+ };
+
+ enum NavigationMode {
+ NavChannel,
+ NavZoom,
+ NavScroll,
+ NavBank,
+ NavMaster,
+ NavSection,
+ NavMarker
+ };
+
+ enum MixMode {
+ MixAudio,
+ MixInstrument,
+ MixBus,
+ MixVCA,
+ MixAll,
+ MixInputs,
+ MixMIDI,
+ MixOutputs,
+ MixFX,
+ MixUser,
+ };
+
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_fp8base_h_ */
diff --git a/libs/surfaces/faderport8/fp8_button.h b/libs/surfaces/faderport8/fp8_button.h
new file mode 100644
index 0000000000..a817dd8e79
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_button.h
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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_surfaces_fp8button_h_
+#define _ardour_surfaces_fp8button_h_
+
+#include <stdint.h>
+
+#include "pbd/base_ui.h"
+#include "pbd/signals.h"
+
+#include "fp8_base.h"
+
+namespace ArdourSurface {
+
+class FP8ButtonInterface
+{
+public:
+ FP8ButtonInterface () {}
+ virtual ~FP8ButtonInterface () {}
+
+ /* user API */
+ PBD::Signal0<void> pressed;
+ PBD::Signal0<void> released;
+
+ virtual bool is_pressed () const { return false; }
+ virtual bool is_active () const { return false; }
+
+ virtual void ignore_release () {}
+
+ /* internal API - called from midi thread,
+ * user pressed/released button the device
+ */
+ virtual bool midi_event (bool) = 0;
+
+ /* internal API - called from surface thread
+ * set Light on the button
+ */
+ virtual void set_active (bool a) = 0;
+ virtual void set_color (uint32_t rgba) {}
+ virtual void set_blinking (bool) {}
+
+ static bool force_change; // used during init
+};
+
+class FP8DummyButton : public FP8ButtonInterface
+{
+public:
+ virtual void set_active (bool a) {}
+ virtual bool midi_event (bool) { return false; }
+};
+
+
+class FP8ButtonBase : public FP8ButtonInterface
+{
+public:
+ FP8ButtonBase (FP8Base& b)
+ : _base (b)
+ , _pressed (false)
+ , _active (false)
+ , _ignore_release (false)
+ , _rgba (0)
+ , _blinking (false)
+ { }
+
+ bool is_pressed () const { return _pressed; }
+ bool is_active () const { return _active; }
+
+ virtual bool midi_event (bool a)
+ {
+ if (a == _pressed) {
+ return false;
+ }
+ _pressed = a;
+ if (a) {
+ pressed (); /* EMIT SIGNAL */
+ } else {
+ if (_ignore_release) {
+ _ignore_release = false;
+ } else {
+ released (); /* EMIT SIGNAL */
+ }
+ }
+ return true;
+ }
+
+ void ignore_release () {
+ if (_pressed) {
+ _ignore_release = true;
+ }
+ }
+
+ void set_blinking (bool yes) {
+ if (yes && !_blinking) {
+ _blinking = true;
+ _base.BlinkIt.connect_same_thread (_blink_connection, boost::bind (&FP8ButtonBase::blink, this, _1));
+ } else if (!yes && _blinking) {
+ _blink_connection.disconnect ();
+ blink (true);
+ _blinking = false;
+ }
+ }
+
+protected:
+ FP8Base& _base;
+ bool _pressed;
+ bool _active;
+ bool _ignore_release;
+ uint32_t _rgba;
+ virtual void blink (bool onoff) = 0;
+
+private:
+ PBD::ScopedConnection _blink_connection;
+ bool _blinking;
+};
+
+class FP8Button : public FP8ButtonBase
+{
+public:
+ FP8Button (FP8Base& b, uint8_t id, bool color = false)
+ : FP8ButtonBase (b)
+ , _midi_id (id)
+ , _has_color (color)
+ { }
+
+ virtual void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void set_color (uint32_t rgba)
+ {
+ if (!_has_color || _rgba == rgba) {
+ return;
+ }
+ _rgba = rgba;
+ _base.tx_midi3 (0x91, _midi_id, (_rgba >> 25) & 0x7f);
+ _base.tx_midi3 (0x92, _midi_id, (_rgba >> 17) & 0x7f);
+ _base.tx_midi3 (0x93, _midi_id, (_rgba >> 9) & 0x7f);
+ }
+
+protected:
+ void blink (bool onoff)
+ {
+ if (!_active) { return; }
+ _base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
+ }
+
+ uint8_t _midi_id; // MIDI-note
+ bool _has_color;
+};
+
+class FP8ReadOnlyButton : public FP8Button
+{
+public:
+ FP8ReadOnlyButton (FP8Base& b, uint8_t id, bool color = false)
+ : FP8Button (b, id, color)
+ {}
+
+ void set_active (bool) { }
+};
+
+/* virtual button. used for shift toggle. */
+class ShadowButton : public FP8ButtonBase
+{
+public:
+ ShadowButton (FP8Base& b)
+ : FP8ButtonBase (b)
+ {}
+
+ PBD::Signal1<void, bool> ActiveChanged;
+ PBD::Signal0<void> ColourChanged;
+
+ uint32_t color () const { return _rgba; }
+
+ bool midi_event (bool a)
+ {
+ assert (0);
+ return false;
+ }
+
+ bool set_pressed (bool a)
+ {
+ return FP8ButtonBase::midi_event (a);
+ }
+
+ void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ ActiveChanged (a); /* EMIT SIGNAL */
+ }
+
+ void set_color (uint32_t rgba)
+ {
+ if (_rgba == rgba) {
+ return;
+ }
+ _rgba = rgba;
+ ColourChanged ();
+ }
+
+protected:
+ void blink (bool onoff) {
+ if (!_active) { return; }
+ ActiveChanged (onoff);
+ }
+};
+
+/* Wraps 2 buttons with the same physical MIDI ID */
+class FP8DualButton : public FP8ButtonInterface
+{
+public:
+ FP8DualButton (FP8Base& b, uint8_t id, bool color = false)
+ : _base (b)
+ , _b0 (b)
+ , _b1 (b)
+ , _midi_id (id)
+ , _has_color (color)
+ , _rgba (0)
+ , _shift (false)
+ {
+ _b0.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, false, _1));
+ _b1.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, true, _1));
+ if (_has_color) {
+ _b0.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, false));
+ _b1.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, true));
+ }
+ }
+
+ bool midi_event (bool a) {
+ return (_shift ? _b1 : _b0).set_pressed (a);
+ }
+
+ void set_active (bool a) {
+ /* This button is never directly used
+ * by the libardour side API.
+ */
+ assert (0);
+ }
+
+ void active_changed (bool s, bool a) {
+ if (s != _shift) {
+ return;
+ }
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void colour_changed (bool s) {
+ if (s != _shift || !_has_color) {
+ return;
+ }
+ uint32_t rgba = (_shift ? _b1 : _b0).color ();
+ if (rgba == _rgba) {
+ return;
+ }
+ _rgba = rgba;
+ _base.tx_midi3 (0x91, _midi_id, (rgba >> 25) & 0x7f);
+ _base.tx_midi3 (0x92, _midi_id, (rgba >> 17) & 0x7f);
+ _base.tx_midi3 (0x93, _midi_id, (rgba >> 9) & 0x7f);
+ }
+
+ FP8ButtonInterface* button () { return &_b0; }
+ FP8ButtonInterface* button_shift () { return &_b1; }
+
+protected:
+ FP8Base& _base;
+
+ virtual void connect_toggle () = 0;
+
+ void shift_changed (bool shift) {
+ if (_shift == shift) {
+ return;
+ }
+ (_shift ? _b1 : _b0).set_pressed (false);
+ _shift = shift;
+ active_changed (_shift, (_shift ? _b1 : _b0).is_active());
+ colour_changed (_shift);
+ }
+
+private:
+ ShadowButton _b0;
+ ShadowButton _b1;
+ uint8_t _midi_id; // MIDI-note
+ bool _has_color;
+ uint32_t _rgba;
+ bool _shift;
+ PBD::ScopedConnectionList _button_connections;
+};
+
+class FP8ShiftSensitiveButton : public FP8DualButton
+{
+public:
+ FP8ShiftSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
+ :FP8DualButton (b, id, color)
+ {
+ connect_toggle ();
+ }
+
+protected:
+ void connect_toggle ()
+ {
+ _base.ShiftButtonChange.connect_same_thread (_shift_connection, boost::bind (&FP8ShiftSensitiveButton::shift_changed, this, _1));
+ }
+
+private:
+ PBD::ScopedConnection _shift_connection;
+};
+
+class FP8ARMSensitiveButton : public FP8DualButton
+{
+public:
+ FP8ARMSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
+ :FP8DualButton (b, id, color)
+ {
+ connect_toggle ();
+ }
+
+protected:
+ void connect_toggle ()
+ {
+ _base.ARMButtonChange.connect_same_thread (_arm_connection, boost::bind (&FP8ARMSensitiveButton::shift_changed, this, _1));
+ }
+
+private:
+ PBD::ScopedConnection _arm_connection;
+};
+
+
+// short press: activate in press, deactivate on release,
+// long press + hold, activate on press, de-activate directly on release
+// e.g. mute/solo press + hold => changed()
+class FP8MomentaryButton : public FP8ButtonInterface
+{
+public:
+ FP8MomentaryButton (FP8Base& b, uint8_t id)
+ : _base (b)
+ , _midi_id (id)
+ , _pressed (false)
+ , _active (false)
+ {}
+
+ ~FP8MomentaryButton () {
+ _hold_connection.disconnect ();
+ }
+
+ PBD::Signal1<void, bool> StateChange;
+
+ void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void reset ()
+ {
+ _was_active_on_press = false;
+ _hold_connection.disconnect ();
+ }
+
+ bool midi_event (bool a)
+ {
+ if (a == _pressed) {
+ return false;
+ }
+
+ _pressed = a;
+
+ if (a) {
+ _was_active_on_press = _active;
+ }
+
+ if (a && !_active) {
+ _momentaty = false;
+ StateChange (true); /* EMIT SIGNAL */
+ Glib::RefPtr<Glib::TimeoutSource> hold_timer =
+ Glib::TimeoutSource::create (500);
+ hold_timer->attach (fp8_loop()->get_context());
+ _hold_connection = hold_timer->connect (sigc::mem_fun (*this, &FP8MomentaryButton::hold_timeout));
+ } else if (!a && _was_active_on_press) {
+ _hold_connection.disconnect ();
+ _momentaty = false;
+ StateChange (false); /* EMIT SIGNAL */
+ } else if (!a && _momentaty) {
+ _hold_connection.disconnect ();
+ _momentaty = false;
+ StateChange (false); /* EMIT SIGNAL */
+ }
+ return true;
+ }
+
+protected:
+ FP8Base& _base;
+ uint8_t _midi_id; // MIDI-note
+ bool _pressed;
+ bool _momentaty;
+ bool _was_active_on_press;
+ bool _active;
+
+private:
+ bool hold_timeout ()
+ {
+ _momentaty = true;
+ return false;
+ }
+ sigc::connection _hold_connection;
+};
+
+class FP8RepeatButton : public FP8Button
+{
+public:
+ FP8RepeatButton (FP8Base& b, uint8_t id, bool color = false)
+ : FP8Button (b, id, color)
+ , _skip (0)
+ {}
+
+ ~FP8RepeatButton ()
+ {
+ stop_repeat ();
+ }
+
+ bool midi_event (bool a)
+ {
+ bool rv = FP8Button::midi_event (a);
+ if (rv && a) {
+ start_repeat ();
+ }
+ return rv;
+ }
+
+ void stop_repeat ()
+ {
+ _press_timeout_connection.disconnect ();
+ }
+
+private:
+ void start_repeat ()
+ {
+ stop_repeat ();
+ _skip = 5;
+ Glib::RefPtr<Glib::TimeoutSource> press_timer =
+ Glib::TimeoutSource::create (100);
+ press_timer->attach (fp8_loop()->get_context());
+ _press_timeout_connection = press_timer->connect (sigc::mem_fun (*this, &FP8RepeatButton::repeat_press));
+ }
+
+ bool repeat_press ()
+ {
+ if (!_pressed) {
+ return false;
+ }
+ if (_skip > 0) {
+ --_skip;
+ return true;
+ }
+ pressed ();
+ return true;
+ }
+
+ int _skip;
+ sigc::connection _press_timeout_connection;
+};
+
+
+} /* namespace */
+#endif /* _ardour_surfaces_fp8button_h_ */
diff --git a/libs/surfaces/faderport8/fp8_controls.cc b/libs/surfaces/faderport8/fp8_controls.cc
new file mode 100644
index 0000000000..5a06dc75e5
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_controls.cc
@@ -0,0 +1,413 @@
+/* Faderport 8 Control Surface
+ * Abstraction of Surface Control Elements.
+ *
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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 "fp8_controls.h"
+
+using namespace ArdourSurface;
+using namespace ArdourSurface::FP8Types;
+
+bool FP8ButtonInterface::force_change = false;
+
+#define NEWBUTTON(midi_id, button_id, color) \
+ do { \
+ assert (_midimap.end() == _midimap.find (midi_id)); \
+ assert (_ctrlmap.end() == _ctrlmap.find (button_id)); \
+ FP8Button *t = new FP8Button (b, midi_id); \
+ _midimap[midi_id] = t; \
+ _ctrlmap[button_id] = t; \
+ } while (0)
+
+
+#define NEWTYPEBUTTON(TYPE, midi_id, button_id, color) \
+ do { \
+ assert (_midimap.end() == _midimap.find (midi_id)); \
+ assert (_ctrlmap.end() == _ctrlmap.find (button_id)); \
+ TYPE *t = new TYPE (b, midi_id); \
+ _midimap[midi_id] = t; \
+ _ctrlmap[button_id] = t; \
+ } while (0)
+
+
+
+#define NEWSHIFTBUTTON(midi_id, id1, id2, color) \
+ do { \
+ assert (_midimap.end() == _midimap.find (midi_id)); \
+ assert (_ctrlmap.end() == _ctrlmap.find (id1)); \
+ assert (_ctrlmap.end() == _ctrlmap.find (id2)); \
+ FP8ShiftSensitiveButton *t = \
+ new FP8ShiftSensitiveButton (b, midi_id, color); \
+ _midimap[midi_id] = t; \
+ _ctrlmap[id1] = t->button (); \
+ _ctrlmap[id2] = t->button_shift (); \
+ } while (0)
+
+
+FP8Controls::FP8Controls (FP8Base& b)
+ : _fadermode (ModeTrack)
+ , _navmode (NavMaster)
+ , _mixmode (MixAll)
+ , _display_timecode (false)
+{
+ NEWBUTTON (0x56, BtnLoop, false);
+ NEWTYPEBUTTON (FP8RepeatButton, 0x5b, BtnRewind, false);
+ NEWTYPEBUTTON (FP8RepeatButton, 0x5c, BtnFastForward, false);
+ NEWBUTTON (0x5d, BtnStop, false);
+ NEWBUTTON (0x5e, BtnPlay, false);
+ NEWBUTTON (0x5f, BtnRecord, false);
+
+ NEWSHIFTBUTTON (0x4a, BtnARead, BtnUser3, true);
+ NEWSHIFTBUTTON (0x4b, BtnAWrite, BtnUser2, true);
+ NEWSHIFTBUTTON (0x4c, BtnATrim, BtnRedo, true);
+ NEWSHIFTBUTTON (0x4d, BtnATouch, BtnUser1, true);
+ NEWSHIFTBUTTON (0x4e, BtnALatch, BtnSave, true);
+ NEWSHIFTBUTTON (0x4f, BtnAOff, BtnUndo, true);
+
+ NEWBUTTON (0x2e, BtnPrev, false);
+ NEWBUTTON (0x2f, BtnNext, false);
+
+ NEWSHIFTBUTTON (0x36, BtnChannel, BtnF1, false);
+ NEWSHIFTBUTTON (0x37, BtnZoom, BtnF2, false);
+ NEWSHIFTBUTTON (0x38, BtnScroll, BtnF3, false);
+ NEWSHIFTBUTTON (0x39, BtnBank, BtnF4, false);
+ NEWSHIFTBUTTON (0x3a, BtnMaster, BtnF5, false);
+ NEWSHIFTBUTTON (0x3b, BtnClick, BtnF6, false);
+ NEWSHIFTBUTTON (0x3c, BtnSection, BtnF7, false);
+ NEWSHIFTBUTTON (0x3d, BtnMarker, BtnF8, false);
+
+ NEWSHIFTBUTTON (0x28, BtnTrack, BtnTimecode, false);
+ NEWBUTTON (0x2b, BtnPlugins, false);
+ NEWBUTTON (0x29, BtnSend, false);
+ NEWBUTTON (0x2a, BtnPan, false);
+
+ NEWSHIFTBUTTON (0x00, BtnArm, BtnArmAll, false);
+ NEWBUTTON (0x01, BtnSoloClear, false);
+ NEWBUTTON (0x02, BtnMuteClear, false);
+
+ NEWSHIFTBUTTON (0x03, BtnBypass, BtnBypassAll, true);
+ NEWSHIFTBUTTON (0x04, BtnMacro, BtnOpen, true);
+ NEWSHIFTBUTTON (0x05, BtnLink, BtnLock, true);
+
+ NEWSHIFTBUTTON (0x3e, BtnMAudio, BtnMInputs, true);
+ NEWSHIFTBUTTON (0x3f, BtnMVI, BtnMMIDI, true);
+ NEWSHIFTBUTTON (0x40, BtnMBus, BtnMOutputs, true);
+ NEWSHIFTBUTTON (0x41, BtnMVCA, BtnMFX, true);
+ NEWSHIFTBUTTON (0x42, BtnMAll, BtnMUser, true);
+
+ NEWTYPEBUTTON (FP8ReadOnlyButton, 0x53, BtnEncoder, false);
+ NEWTYPEBUTTON (FP8ReadOnlyButton, 0x20, BtnParam, false);
+ NEWTYPEBUTTON (FP8ReadOnlyButton, 0x66, BtnFootswitch, false);
+
+ /* internal bindings */
+
+#define BindMethod(ID, CB) \
+ button (ID).released.connect_same_thread (button_connections, boost::bind (&FP8Controls:: CB, this));
+
+ BindMethod (FP8Controls::BtnTimecode, toggle_timecode);
+
+#define BindNav(BTN, MODE)\
+ button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_nav_mode, this, MODE))
+
+ BindNav (BtnChannel, NavChannel);
+ BindNav (BtnZoom, NavZoom);
+ BindNav (BtnScroll, NavScroll);
+ BindNav (BtnBank, NavBank);
+ BindNav (BtnMaster, NavMaster);
+ BindNav (BtnSection, NavSection);
+ BindNav (BtnMarker, NavMarker);
+
+#define BindFader(BTN, MODE)\
+ button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_fader_mode, this, MODE))
+
+ BindFader (BtnTrack, ModeTrack);
+ BindFader (BtnPlugins, ModePlugins);
+ BindFader (BtnSend, ModeSend);
+ BindFader (BtnPan, ModePan);
+
+
+#define BindMix(BTN, MODE)\
+ button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_mix_mode, this, MODE))
+
+ BindMix (BtnMAudio, MixAudio);
+ BindMix (BtnMVI, MixInstrument);
+ BindMix (BtnMBus, MixBus);
+ BindMix (BtnMVCA, MixVCA);
+ BindMix (BtnMAll, MixAll);
+ BindMix (BtnMInputs, MixInputs);
+ BindMix (BtnMMIDI, MixMIDI);
+ BindMix (BtnMOutputs, MixOutputs);
+ BindMix (BtnMFX, MixFX);
+ BindMix (BtnMUser, MixUser);
+
+ /* create channelstrips */
+ for (uint8_t id = 0; id < 8; ++id) {
+ chanstrip[id] = new FP8Strip (b, id);
+ _midimap_strip[0x08 + id] = &(chanstrip[id]->solo_button());
+ _midimap_strip[0x10 + id] = &(chanstrip[id]->mute_button());
+ _midimap_strip[0x18 + id] = &(chanstrip[id]->selrec_button());
+ }
+
+ /* set User button names */
+
+#define REGISTER_ENUM(ID, NAME) \
+ _user_str_to_enum[#ID] = ID; \
+ _user_enum_to_str[ID] = #ID; \
+ _user_buttons[ID] = NAME;
+
+ REGISTER_ENUM (BtnFootswitch, "Footswitch");
+ REGISTER_ENUM (BtnUser1 , "User 1");
+ REGISTER_ENUM (BtnUser2 , "User 2");
+ REGISTER_ENUM (BtnUser3 , "User 3");
+ REGISTER_ENUM (BtnF1 , "F1");
+ REGISTER_ENUM (BtnF2 , "F2");
+ REGISTER_ENUM (BtnF3 , "F3");
+ REGISTER_ENUM (BtnF4 , "F4");
+ REGISTER_ENUM (BtnF5 , "F5");
+ REGISTER_ENUM (BtnF6 , "F6");
+ REGISTER_ENUM (BtnF7 , "F7");
+ REGISTER_ENUM (BtnF8 , "F8");
+#undef REGISTER_ENUM
+}
+
+FP8Controls::~FP8Controls ()
+{
+ for (MidiButtonMap::const_iterator i = _midimap.begin (); i != _midimap.end (); ++i) {
+ delete i->second;
+ }
+ for (uint8_t id = 0; id < 8; ++id) {
+ delete chanstrip[id];
+ }
+ _midimap_strip.clear ();
+ _ctrlmap.clear ();
+ _midimap.clear ();
+}
+
+bool
+FP8Controls::button_name_to_enum (std::string const& n, ButtonId& id) const
+{
+ std::map<std::string, ButtonId>::const_iterator i = _user_str_to_enum.find (n);
+ if (i == _user_str_to_enum.end()) {
+ return false;
+ }
+ id = i->second;
+ return true;
+}
+
+bool
+FP8Controls::button_enum_to_name (ButtonId id, std::string& n) const
+{
+ std::map<ButtonId, std::string>::const_iterator i = _user_enum_to_str.find (id);
+ if (i == _user_enum_to_str.end()) {
+ return false;
+ }
+ n = i->second;
+ return true;
+}
+
+void
+FP8Controls::initialize ()
+{
+ FP8ButtonInterface::force_change = true;
+ /* set RGB colors */
+ button (BtnUndo).set_color (0x00ff00ff);
+ button (BtnRedo).set_color (0x00ff00ff);
+
+ button (BtnAOff).set_color (0xffffffff);
+ button (BtnATrim).set_color (0x000030ff);
+ button (BtnARead).set_color (0x00ff00ff);
+ button (BtnAWrite).set_color (0xff0000ff);
+ button (BtnATouch).set_color (0xff8800ff);
+
+ button (BtnUser1).set_color (0x0000ffff);
+ button (BtnUser2).set_color (0x0000ffff);
+ button (BtnUser3).set_color (0x0000ffff);
+
+ button (BtnALatch).set_color (0x0000ffff);
+
+ button (BtnBypass).set_color (0x888888ff);
+ button (BtnBypassAll).set_color (0xffffffff);
+
+ button (BtnMacro).set_color (0x888888ff);
+ button (BtnOpen).set_color (0xffffffff);
+
+ button (BtnLink).set_color (0x888888ff);
+ button (BtnLock).set_color (0xffffffff);
+
+ button (BtnMAudio).set_color (0x0000ffff);
+ button (BtnMVI).set_color (0x0000ffff);
+ button (BtnMBus).set_color (0x0000ffff);
+ button (BtnMVCA).set_color (0x0000ffff);
+ button (BtnMAll).set_color (0x0000ffff);
+
+ button (BtnMInputs).set_color (0x0000ffff);
+ button (BtnMMIDI).set_color (0x0000ffff);
+ button (BtnMOutputs).set_color (0x0000ffff);
+ button (BtnMFX).set_color (0x0000ffff);
+ button (BtnMUser).set_color (0x0000ffff);
+
+ for (uint8_t id = 0; id < 8; ++id) {
+ chanstrip[id]->initialize ();
+ }
+
+ /* initally turn all lights off */
+ for (CtrlButtonMap::const_iterator i = _ctrlmap.begin (); i != _ctrlmap.end (); ++i) {
+ i->second->set_active (false);
+ }
+
+ /* default modes */
+ button (BtnMaster).set_active (true);
+ button (BtnTrack).set_active (true);
+ button (BtnMAll).set_active (true);
+ button (BtnTimecode).set_active (_display_timecode);
+
+ FP8ButtonInterface::force_change = false;
+}
+
+FP8ButtonInterface&
+FP8Controls::button (ButtonId id)
+{
+ CtrlButtonMap::const_iterator i = _ctrlmap.find (id);
+ if (i == _ctrlmap.end()) {
+ assert (0);
+ return _dummy_button;
+ }
+ return *(i->second);
+}
+
+FP8Strip&
+FP8Controls::strip (uint8_t id)
+{
+ assert (id < 8);
+ return *chanstrip[id];
+}
+
+/* *****************************************************************************
+ * Delegate MIDI events
+ */
+
+bool
+FP8Controls::midi_event (uint8_t id, uint8_t val)
+{
+ MidiButtonMap::const_iterator i;
+
+ i = _midimap_strip.find (id);
+ if (i != _midimap_strip.end()) {
+ return i->second->midi_event (val > 0x40);
+ }
+
+ i = _midimap.find (id);
+ if (i != _midimap.end()) {
+ return i->second->midi_event (val > 0x40);
+ }
+ return false;
+}
+
+bool
+FP8Controls::midi_touch (uint8_t id, uint8_t val)
+{
+ assert (id < 8);
+ return chanstrip[id]->midi_touch (val > 0x40);
+}
+
+bool
+FP8Controls::midi_fader (uint8_t id, unsigned short val)
+{
+ assert (id < 8);
+ return chanstrip[id]->midi_fader ((val >> 4) / 1023.f);
+}
+
+/* *****************************************************************************
+ * Internal Model + View for Modes
+ */
+
+void
+FP8Controls::set_nav_mode (NavigationMode m)
+{
+ if (_navmode == m) {
+ return;
+ }
+ // TODO add special-cases:
+ // - master/monitor (blink when button is held + monitor section present)
+ // - "click" hold -> encoder sets click volume, encoder-press toggle rec-only-metro
+ button (BtnChannel).set_active (m == NavChannel);
+ button (BtnZoom).set_active (m == NavZoom);
+ button (BtnScroll).set_active (m == NavScroll);
+ button (BtnBank).set_active (m == NavBank);
+ button (BtnMaster).set_active (m == NavMaster);
+ button (BtnSection).set_active (m == NavSection);
+ button (BtnMarker).set_active (m == NavMarker);
+ _navmode = m;
+}
+
+void
+FP8Controls::set_fader_mode (FaderMode m)
+{
+ if (_fadermode == m) {
+ if (m == ModePlugins || m == ModeSend) {
+ /* "Edit Plugins" while editing Plugin-params, returns back
+ * to plugin selection.
+ * "Sends" button banks through sends.
+ */
+ FaderModeChanged ();
+ }
+ return;
+ }
+ // set lights
+ button (BtnTrack).set_active (m == ModeTrack);
+ button (BtnPlugins).set_active (m == ModePlugins);
+ button (BtnSend).set_active (m == ModeSend);
+ button (BtnPan).set_active (m == ModePan);
+ _fadermode = m;
+ FaderModeChanged ();
+}
+
+void
+FP8Controls::set_mix_mode (MixMode m)
+{
+ if (_mixmode == m) {
+ if (m == MixUser || m == MixInputs) {
+ /* always re-assign:
+ * - MixUser: depends on selection
+ * - MixInputs: depends on rec-arm
+ */
+ MixModeChanged ();
+ }
+ return;
+ }
+ button (BtnMAudio).set_active (m == MixAudio);
+ button (BtnMVI).set_active (m == MixInstrument);
+ button (BtnMBus).set_active (m == MixBus);
+ button (BtnMVCA).set_active (m == MixVCA);
+ button (BtnMAll).set_active (m == MixAll);
+ button (BtnMInputs).set_active (m == MixInputs);
+ button (BtnMMIDI).set_active (m == MixMIDI);
+ button (BtnMOutputs).set_active (m == MixOutputs);
+ button (BtnMFX).set_active (m == MixFX);
+ button (BtnMUser).set_active (m == MixUser);
+
+ _mixmode = m;
+ MixModeChanged ();
+}
+
+void
+FP8Controls::toggle_timecode ()
+{
+ _display_timecode = !_display_timecode;
+ button (BtnTimecode).set_active (_display_timecode);
+}
diff --git a/libs/surfaces/faderport8/fp8_controls.h b/libs/surfaces/faderport8/fp8_controls.h
new file mode 100644
index 0000000000..157435b059
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_controls.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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_surfaces_fp8controls_h_
+#define _ardour_surfaces_fp8controls_h_
+
+#include <map>
+
+#include "fp8_base.h"
+#include "fp8_button.h"
+#include "fp8_strip.h"
+
+namespace ArdourSurface {
+
+class FP8Controls
+{
+public:
+ FP8Controls (FP8Base&);
+ virtual ~FP8Controls ();
+
+ enum ButtonId {
+ BtnPlay,
+ BtnStop,
+ BtnRecord,
+ BtnLoop,
+ BtnRewind,
+ BtnFastForward,
+
+ BtnALatch,
+ BtnATrim,
+ BtnAOff,
+ BtnATouch,
+ BtnAWrite,
+ BtnARead,
+
+ // Automation
+ BtnSave,
+ BtnRedo,
+ BtnUndo,
+ BtnUser1,
+ BtnUser2,
+ BtnUser3,
+
+ BtnFootswitch,
+
+ // Pan/Param encoder press
+ BtnParam,
+
+ // Navigation
+ BtnPrev,
+ BtnNext,
+ BtnEncoder,
+
+ BtnChannel,
+ BtnZoom,
+ BtnScroll,
+ BtnBank,
+ BtnMaster,
+ BtnClick,
+ BtnSection,
+ BtnMarker,
+
+ BtnF1, BtnF2, BtnF3, BtnF4,
+ BtnF5, BtnF6, BtnF7, BtnF8,
+
+ // FaderMode
+ BtnTrack,
+ BtnPlugins,
+ BtnSend,
+ BtnPan,
+
+ BtnTimecode,
+
+ // Mix Management
+ BtnMAudio,
+ BtnMVI,
+ BtnMBus,
+ BtnMVCA,
+ BtnMAll,
+
+ BtnMInputs,
+ BtnMMIDI,
+ BtnMOutputs,
+ BtnMFX,
+ BtnMUser,
+
+ // General Controls
+ BtnArm,
+ BtnArmAll,
+ BtnSoloClear,
+ BtnMuteClear,
+
+ BtnBypass,
+ BtnBypassAll,
+ BtnMacro,
+ BtnOpen,
+ BtnLink,
+ BtnLock,
+
+ };
+
+ typedef std::map <ButtonId, std::string> UserButtonMap;
+
+ UserButtonMap const& user_buttons () const {
+ return _user_buttons;
+ }
+
+ bool button_name_to_enum (std::string const&, ButtonId&) const;
+ bool button_enum_to_name (ButtonId, std::string&) const;
+
+ PBD::Signal0<void> FaderModeChanged;
+ PBD::Signal0<void> MixModeChanged;
+
+ FP8Types::FaderMode fader_mode () const { return _fadermode; }
+ FP8Types::NavigationMode nav_mode () const { return _navmode; }
+ FP8Types::MixMode mix_mode () const { return _mixmode; }
+ bool display_timecode () const { return _display_timecode; }
+
+ FP8ButtonInterface& button (ButtonId id);
+ FP8Strip& strip (uint8_t id);
+
+ bool midi_event (uint8_t id, uint8_t val);
+ bool midi_touch (uint8_t id, uint8_t val);
+ bool midi_fader (uint8_t id, unsigned short val);
+ void initialize ();
+
+ void set_fader_mode (FP8Types::FaderMode);
+protected:
+ typedef std::map <uint8_t, FP8ButtonInterface*> MidiButtonMap;
+ typedef std::map <ButtonId, FP8ButtonInterface*> CtrlButtonMap;
+
+ void set_nav_mode (FP8Types::NavigationMode);
+ void set_mix_mode (FP8Types::MixMode);
+ void toggle_timecode ();
+
+ MidiButtonMap _midimap;
+ CtrlButtonMap _ctrlmap;
+ MidiButtonMap _midimap_strip;
+
+ FP8Strip* chanstrip[8];
+
+ FP8Types::FaderMode _fadermode;
+ FP8Types::NavigationMode _navmode;
+ FP8Types::MixMode _mixmode;
+ bool _display_timecode;
+
+ UserButtonMap _user_buttons;
+ FP8DummyButton _dummy_button;
+
+ std::map<std::string, ButtonId> _user_str_to_enum;
+ std::map<ButtonId, std::string> _user_enum_to_str;
+
+ PBD::ScopedConnectionList button_connections;
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_fp8controls_h_ */
diff --git a/libs/surfaces/faderport8/fp8_strip.cc b/libs/surfaces/faderport8/fp8_strip.cc
new file mode 100644
index 0000000000..68a9e7262b
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_strip.cc
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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 "ardour/automation_control.h"
+#include "ardour/gain_control.h"
+#include "ardour/meter.h"
+#include "ardour/mute_control.h"
+#include "ardour/plugin_insert.h"
+#include "ardour/session.h"
+#include "ardour/solo_control.h"
+#include "ardour/stripable.h"
+#include "ardour/track.h"
+#include "ardour/value_as_string.h"
+
+#include "control_protocol/control_protocol.h"
+
+#include "fp8_strip.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace ArdourSurface::FP8Types;
+
+FP8Strip::FP8Strip (FP8Base& b, uint8_t id)
+ : _base (b)
+ , _id (id)
+ , _solo (b, 0x08 + id)
+ , _mute (b, 0x10 + id)
+ , _selrec (b, 0x18 + id, true)
+ , _touching (false)
+ , _strip_mode (0)
+ , _bar_mode (0)
+ , _displaymode (Stripables)
+{
+ assert (id < 8);
+
+ _last_fader = 65535;
+ _last_meter = _last_redux = _last_panpos = 0xff;
+
+ _mute.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_mute, this, _1));
+ _solo.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_solo, this, _1));
+ select_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_select, this));
+ recarm_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_recarm, this));
+ b.Periodic.connect_same_thread (_base_connection, boost::bind (&FP8Strip::periodic, this));
+}
+
+FP8Strip::~FP8Strip ()
+{
+ _fader_connection.disconnect ();
+ _mute_connection.disconnect ();
+ _solo_connection.disconnect ();
+ _rec_connection.disconnect ();
+ _pan_connection.disconnect ();
+
+ _fader_ctrl.reset ();
+ _mute_ctrl.reset ();
+ _solo_ctrl.reset ();
+ _rec_ctrl.reset ();
+ _pan_ctrl.reset ();
+
+ _base_connection.disconnect ();
+ _button_connections.drop_connections ();
+}
+
+void
+FP8Strip::initialize ()
+{
+ /* this is called once midi transmission is possible,
+ * ie from FaderPort8::connected()
+ */
+ _solo.set_active (false);
+ _mute.set_active (false);
+
+ /* reset momentary button state */
+ _mute.reset ();
+ _solo.reset ();
+
+ /* clear cached values */
+ _last_fader = 65535;
+ _last_meter = _last_redux = _last_panpos = 0xff;
+
+ select_button ().set_color (0xffffffff);
+ select_button ().set_active (false);
+ select_button ().set_blinking (false);
+
+ recarm_button ().set_active (false);
+ recarm_button ().set_color (0xffffffff);
+
+ set_strip_mode (0, true);
+
+ // force unset txt
+ _last_line[0].clear ();
+ _last_line[1].clear ();
+ _last_line[2].clear ();
+ _last_line[3].clear ();
+ _base.tx_sysex (4, 0x12, _id, 0x00, 0x00);
+ _base.tx_sysex (4, 0x12, _id, 0x01, 0x00);
+ _base.tx_sysex (4, 0x12, _id, 0x02, 0x00);
+ _base.tx_sysex (4, 0x12, _id, 0x03, 0x00);
+
+ set_bar_mode (4); // off
+
+ _base.tx_midi2 (0xd0 + _id, 0); // reset meter
+ _base.tx_midi2 (0xd8 + _id, 0); // reset redux
+
+ _base.tx_midi3 (0xe0 + _id, 0, 0); // fader
+}
+
+
+#define GENERATE_SET_CTRL_FUNCTION(NAME) \
+void \
+FP8Strip::set_ ##NAME##_controllable (boost::shared_ptr<AutomationControl> ac) \
+{ \
+ if (_##NAME##_ctrl == ac) { \
+ return; \
+ } \
+ _##NAME##_connection.disconnect(); \
+ _##NAME##_ctrl = ac; \
+ \
+ if (ac) { \
+ ac->Changed.connect (_##NAME##_connection, MISSING_INVALIDATOR, \
+ boost::bind (&FP8Strip::notify_##NAME##_changed, this), fp8_context()); \
+ } \
+ notify_##NAME##_changed (); \
+}
+
+
+GENERATE_SET_CTRL_FUNCTION (fader)
+GENERATE_SET_CTRL_FUNCTION (mute)
+GENERATE_SET_CTRL_FUNCTION (solo)
+GENERATE_SET_CTRL_FUNCTION (rec)
+GENERATE_SET_CTRL_FUNCTION (pan)
+
+#undef GENERATE_SET_CTRL_FUNCTION
+
+void
+FP8Strip::unset_controllables (int which)
+{
+ _peak_meter = boost::shared_ptr<ARDOUR::PeakMeter>();
+ _redux_ctrl = boost::shared_ptr<ARDOUR::ReadOnlyControl>();
+
+ if (which & CTRL_FADER) {
+ set_fader_controllable (boost::shared_ptr<AutomationControl>());
+ }
+ if (which & CTRL_MUTE) {
+ set_mute_controllable (boost::shared_ptr<AutomationControl>());
+ }
+ if (which & CTRL_SOLO) {
+ set_solo_controllable (boost::shared_ptr<AutomationControl>());
+ }
+ if (which & CTRL_REC) {
+ set_rec_controllable (boost::shared_ptr<AutomationControl>());
+ }
+ if (which & CTRL_PAN) {
+ set_bar_mode (4); // off
+ set_pan_controllable (boost::shared_ptr<AutomationControl>());
+ }
+ if (which & CTRL_SELECT) {
+ _select_plugin_functor.clear ();
+ select_button ().set_color (0xffffffff);
+ select_button ().set_active (false);
+ select_button ().set_blinking (false);
+ }
+ if (which & CTRL_TEXT1) {
+ set_text_line (0x00, "");
+ }
+ if (which & CTRL_TEXT2) {
+ set_text_line (0x01, "");
+ }
+ if (which & CTRL_TEXT3) {
+ set_text_line (0x02, "");
+ }
+ if (which & CTRL_TEXT4) {
+ set_text_line (0x03, "");
+ }
+}
+
+void
+FP8Strip::set_stripable (boost::shared_ptr<Stripable> s, bool panmode)
+{
+ assert (s);
+
+ if (panmode) {
+ set_fader_controllable (s->pan_azimuth_control ());
+ } else {
+ set_fader_controllable (s->gain_control ());
+ }
+ set_pan_controllable (s->pan_azimuth_control ());
+
+ if (s->is_monitor ()) {
+ set_mute_controllable (boost::shared_ptr<AutomationControl>());
+ } else {
+ set_mute_controllable (s->mute_control ());
+ }
+ set_solo_controllable (s->solo_control ());
+
+ if (boost::dynamic_pointer_cast<Track> (s)) {
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
+ set_rec_controllable (t->rec_enable_control ());
+ recarm_button ().set_color (0xff0000ff);
+ } else {
+ set_rec_controllable (boost::shared_ptr<AutomationControl>());
+ recarm_button ().set_color (0xffffffff);
+ recarm_button ().set_active (false);
+ }
+ _peak_meter = s->peak_meter ();
+ _redux_ctrl = s->comp_redux_controllable ();
+
+ _select_plugin_functor.clear ();
+ select_button ().set_active (s->is_selected ());
+ select_button ().set_color (s->presentation_info ().color());
+ //select_button ().set_blinking (false);
+
+ set_strip_mode (0x05);
+ set_text_line (0x00, s->name ());
+ set_text_line (0x01, _pan_ctrl ? _pan_ctrl->get_user_string () : "");
+ set_text_line (0x02, "");
+ set_text_line (0x03, "");
+}
+
+void
+FP8Strip::set_select_cb (boost::function<void ()>& functor)
+{
+ _select_plugin_functor.clear ();
+ _select_plugin_functor = functor;
+}
+
+/* *****************************************************************************
+ * Parse Strip Specifig MIDI Events
+ */
+
+bool
+FP8Strip::midi_touch (bool t)
+{
+ _touching = t;
+ boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
+ if (!ac) {
+ return false;
+ }
+ if (t) {
+ ac->start_touch (ac->session().transport_frame());
+ } else {
+ ac->stop_touch (true, ac->session().transport_frame());
+ }
+ return true;
+}
+
+bool
+FP8Strip::midi_fader (float val)
+{
+ assert (val >= 0.f && val <= 1.f);
+ if (!_touching) {
+ return false;
+ }
+ boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
+ if (!ac) {
+ return false;
+ }
+ ac->set_value (ac->interface_to_internal (val), PBD::Controllable::UseGroup);
+ return true;
+}
+
+/* *****************************************************************************
+ * Actions from Controller, Update Model
+ */
+
+void
+FP8Strip::set_mute (bool on)
+{
+ if (_mute_ctrl) {
+ if (!_mute_ctrl->touching ()) {
+ _mute_ctrl->start_touch (_mute_ctrl->session().transport_frame());
+ }
+ _mute_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
+ }
+}
+
+void
+FP8Strip::set_solo (bool on)
+{
+ if (_solo_ctrl) {
+ if (!_solo_ctrl->touching ()) {
+ _solo_ctrl->start_touch (_solo_ctrl->session().transport_frame());
+ }
+ _solo_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
+ }
+}
+
+void
+FP8Strip::set_recarm ()
+{
+ if (_rec_ctrl) {
+ const bool on = !recarm_button().is_active();
+ _rec_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
+ }
+}
+
+
+void
+FP8Strip::set_select ()
+{
+ if (!_select_plugin_functor.empty ()) {
+ _select_plugin_functor ();
+ }
+}
+
+/* *****************************************************************************
+ * Callbacks from Stripable, Update View
+ */
+
+void
+FP8Strip::notify_fader_changed ()
+{
+ boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
+ if (_touching) {
+ return;
+ }
+ float val = 0;
+ if (ac) {
+ val = ac->internal_to_interface (ac->get_value()) * 16368.f; /* 16 * 1023 */
+ }
+ unsigned short mv = lrintf (val);
+ if (mv == _last_fader) {
+ return;
+ }
+ _last_fader = mv;
+ _base.tx_midi3 (0xe0 + _id, (mv & 0x7f), (mv >> 7) & 0x7f);
+}
+
+void
+FP8Strip::notify_solo_changed ()
+{
+ if (_solo_ctrl) {
+ _solo.set_active (_solo_ctrl->get_value () > 0);
+ } else {
+ _solo.set_active (false);
+ }
+}
+
+void
+FP8Strip::notify_mute_changed ()
+{
+ if (_mute_ctrl) {
+ _mute.set_active (_mute_ctrl->get_value () > 0);
+ } else {
+ _mute.set_active (false);
+ }
+}
+
+void
+FP8Strip::notify_rec_changed ()
+{
+ if (_rec_ctrl) {
+ recarm_button ().set_active (_rec_ctrl->get_value() > 0.);
+ } else {
+ recarm_button ().set_active (false);
+ }
+}
+
+void
+FP8Strip::notify_pan_changed ()
+{
+}
+
+/* *****************************************************************************
+ * Periodic View Updates
+ */
+
+void
+FP8Strip::periodic_update_fader ()
+{
+ boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
+ if (!ac || _touching) {
+ return;
+ }
+
+ ARDOUR::AutoState state = ac->automation_state();
+ if (state == Touch || state == Play) {
+ notify_fader_changed ();
+ }
+}
+
+void
+FP8Strip::periodic_update_meter ()
+{
+ bool have_meter = false;
+ bool have_panner = false;
+
+ if (_peak_meter) {
+ have_meter = true;
+ float dB = _peak_meter->meter_level (0, MeterMCP);
+ // TODO: deflect meter
+ int val = std::min (127.f, std::max (0.f, 2.f * dB + 127.f));
+ if (val != _last_meter || val > 0) {
+ _base.tx_midi2 (0xd0 + _id, val & 0x7f); // falls off automatically
+ _last_meter = val;
+ }
+
+ } else {
+ if (0 != _last_meter) {
+ _base.tx_midi2 (0xd0 + _id, 0);
+ _last_meter = 0;
+ }
+ }
+
+ // show redux only if there's a meter, too (strip display mode 5)
+ if (_peak_meter && _redux_ctrl) {
+ float rx = (1.f - _redux_ctrl->get_parameter ()) * 127.f;
+ // TODO: deflect redux
+ int val = std::min (127.f, std::max (0.f, rx));
+ if (val != _last_redux) {
+ _base.tx_midi2 (0xd8 + _id, val & 0x7f);
+ _last_redux = val;
+ }
+ } else {
+ if (0 != _last_redux) {
+ _base.tx_midi2 (0xd8 + _id, 0);
+ _last_redux = 0;
+ }
+ }
+
+ if (_displaymode == PluginParam) {
+ set_bar_mode (4); // Off
+ if (_fader_ctrl) {
+ set_text_line (0x01, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
+ } else {
+ set_text_line (0x01, "");
+ }
+ } else if (_pan_ctrl) {
+ have_panner = true;
+ float panpos = _pan_ctrl->internal_to_interface (_pan_ctrl->get_value());
+ int val = std::min (127.f, std::max (0.f, panpos * 128.f));
+ set_bar_mode (1); // Bipolar
+ if (val != _last_panpos) {
+ _base.tx_midi3 (0xb0, 0x30 + _id, val & 0x7f);
+ _last_panpos = val;
+ }
+ set_text_line (0x01, _pan_ctrl->get_user_string ());
+ } else {
+ set_bar_mode (4); // Off
+ }
+
+ if (have_meter && have_panner) {
+ set_strip_mode (5); // small meter mode
+ }
+ else if (have_meter) {
+ set_strip_mode (4); // big meter mode
+ }
+ else if (have_panner) {
+ set_strip_mode (0); // 3 lines of text + value
+ } else {
+ set_strip_mode (0); // 3 lines of text + value
+ }
+}
+
+void
+FP8Strip::set_strip_mode (uint8_t strip_mode, bool clear)
+{
+ if (strip_mode == _strip_mode && !clear) {
+ return;
+ }
+ _strip_mode = strip_mode;
+ _base.tx_sysex (3, 0x13, _id, (_strip_mode & 0x07) | (clear ? 0x10 : 0));
+ //_base.tx_midi3 (0xb0, 0x38 + _id, _bar_mode);
+}
+
+void
+FP8Strip::set_bar_mode (uint8_t bar_mode)
+{
+ if (bar_mode == _bar_mode) {
+ return;
+ }
+ _bar_mode = bar_mode;
+ _base.tx_midi3 (0xb0, 0x38 + _id, bar_mode);
+}
+
+void
+FP8Strip::set_text_line (uint8_t line, std::string const& txt)
+{
+ assert (line < 4);
+ if (_last_line[line] == txt) {
+ return;
+ }
+ _base.tx_text (_id, line, 0x00, txt);
+ _last_line[line] = txt;
+}
+
+void
+FP8Strip::periodic_update_timecode ()
+{
+ if (_id >= 2 && _id < 6) {
+ std::string const& tc = _base.timecode();
+ //" HH:MM:SS:FF"
+ std::string t;
+ if (tc.size () == 12) {
+ t = tc.substr (1 + (_id - 2) * 3, 2);
+ }
+ set_text_line (0x02, t);
+ }
+}
+
+void
+FP8Strip::periodic ()
+{
+ periodic_update_fader ();
+ periodic_update_meter ();
+ if (_displaymode != PluginSelect) {
+ periodic_update_timecode ();
+ }
+}
diff --git a/libs/surfaces/faderport8/fp8_strip.h b/libs/surfaces/faderport8/fp8_strip.h
new file mode 100644
index 0000000000..fd6d7d10dd
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_strip.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * 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_surfaces_fp8strip_h_
+#define _ardour_surfaces_fp8strip_h_
+
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+
+#include "pbd/signals.h"
+
+#include "fp8_base.h"
+#include "fp8_button.h"
+
+namespace ARDOUR {
+ class Stripable;
+ class AutomationControl;
+ class PeakMeter;
+ class ReadOnlyControl;
+}
+
+namespace ArdourSurface {
+
+class FP8Strip
+{
+public:
+ FP8Strip (FP8Base& b, uint8_t id);
+ ~FP8Strip ();
+
+ FP8ButtonInterface& solo_button () { return _solo; }
+ FP8ButtonInterface& mute_button () { return _mute; }
+ FP8ButtonInterface& selrec_button () { return _selrec; }
+ FP8ButtonInterface& recarm_button () { return *_selrec.button_shift(); }
+ FP8ButtonInterface& select_button () { return *_selrec.button(); }
+
+ bool midi_touch (bool t);
+ bool midi_fader (float val);
+
+ void initialize (); // call only when connected, sends midi
+
+ void set_select_cb (boost::function<void ()>&);
+
+ enum DisplayMode {
+ Stripables,
+ PluginSelect, // no clock display
+ PluginParam, // param value
+ };
+
+ void set_periodic_display_mode (DisplayMode m) {
+ _displaymode = m;
+ }
+
+ // convenience function to call all set_XXX_controllable
+ void set_stripable (boost::shared_ptr<ARDOUR::Stripable>, bool panmode);
+ void set_text_line (uint8_t, std::string const&);
+
+ enum CtrlMask {
+ CTRL_FADER = 0x001,
+ CTRL_MUTE = 0x002,
+ CTRL_SOLO = 0x004,
+ CTRL_REC = 0x004,
+ CTRL_PAN = 0x008,
+ CTRL_SELECT = 0x010,
+ CTRL_TEXT1 = 0x100,
+ CTRL_TEXT2 = 0x200,
+ CTRL_TEXT3 = 0x400,
+ CTRL_TEXT4 = 0x800,
+
+ CTRL_TEXT = 0xf00,
+ CTRL_ALL = 0xfff,
+ };
+
+ void unset_controllables (int which = CTRL_ALL);
+
+ void set_fader_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
+ void set_mute_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
+ void set_solo_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
+ void set_rec_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
+ void set_pan_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
+
+private:
+ FP8Base& _base;
+ uint8_t _id;
+ FP8MomentaryButton _solo;
+ FP8MomentaryButton _mute;
+ FP8ARMSensitiveButton _selrec;
+
+ bool _touching;
+
+ PBD::ScopedConnection _base_connection; // periodic
+ PBD::ScopedConnectionList _button_connections;
+
+ boost::shared_ptr<ARDOUR::Stripable> _stripable;
+
+ boost::shared_ptr<ARDOUR::AutomationControl> _fader_ctrl;
+ boost::shared_ptr<ARDOUR::AutomationControl> _mute_ctrl;
+ boost::shared_ptr<ARDOUR::AutomationControl> _solo_ctrl;
+ boost::shared_ptr<ARDOUR::AutomationControl> _rec_ctrl;
+ boost::shared_ptr<ARDOUR::AutomationControl> _pan_ctrl;
+
+ PBD::ScopedConnection _fader_connection;
+ PBD::ScopedConnection _mute_connection;
+ PBD::ScopedConnection _solo_connection;
+ PBD::ScopedConnection _rec_connection;
+ PBD::ScopedConnection _pan_connection;
+
+ boost::shared_ptr<ARDOUR::PeakMeter> _peak_meter;
+ boost::shared_ptr<ARDOUR::ReadOnlyControl> _redux_ctrl;
+ boost::function<void ()> _select_plugin_functor;
+
+ /* notifications, update view */
+ void notify_fader_changed ();
+ void notify_solo_changed ();
+ void notify_mute_changed ();
+ void notify_rec_changed ();
+ void notify_pan_changed ();
+
+ /* actions, update model */
+ void set_mute (bool);
+ void set_solo (bool);
+ void set_select ();
+ void set_recarm ();
+
+ /* periodic poll, update view */
+ void periodic_update_fader ();
+ void periodic_update_meter ();
+ void periodic_update_timecode ();
+ void periodic ();
+
+ /* cache */
+ unsigned short _last_fader;
+ uint8_t _last_meter;
+ uint8_t _last_redux;
+ uint8_t _last_panpos;
+
+ /* display */
+ void set_strip_mode (uint8_t, bool clear = false);
+ void set_bar_mode (uint8_t);
+
+ uint8_t _strip_mode;
+ uint8_t _bar_mode;
+ DisplayMode _displaymode;
+ std::string _last_line[4];
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_fp8strip_h_ */
diff --git a/libs/surfaces/faderport8/gui.cc b/libs/surfaces/faderport8/gui.cc
new file mode 100644
index 0000000000..8b8673bb3b
--- /dev/null
+++ b/libs/surfaces/faderport8/gui.cc
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <gtkmm/alignment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/liststore.h>
+
+#include "pbd/unwind.h"
+#include "pbd/strsplit.h"
+#include "pbd/file_utils.h"
+
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filesystem_paths.h"
+
+#include "faderport8.h"
+#include "gui.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+
+void*
+FaderPort8::get_gui () const
+{
+ if (!gui) {
+ const_cast<FaderPort8*>(this)->build_gui ();
+ }
+ static_cast<Gtk::VBox*>(gui)->show_all();
+ return gui;
+}
+
+void
+FaderPort8::tear_down_gui ()
+{
+ if (gui) {
+ Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
+ if (w) {
+ w->hide();
+ delete w;
+ }
+ }
+ delete static_cast<FP8GUI*> (gui);
+ gui = 0;
+}
+
+void
+FaderPort8::build_gui ()
+{
+ gui = (void*) new FP8GUI (*this);
+}
+
+/* ****************************************************************************/
+
+FP8GUI::FP8GUI (FaderPort8& p)
+ : fp (p)
+ , table (2, 3)
+ , ignore_active_change (false)
+{
+ set_border_width (12);
+
+ table.set_row_spacings (4);
+ table.set_col_spacings (6);
+ table.set_border_width (12);
+ table.set_homogeneous (false);
+
+ Gtk::Label* l;
+ int row = 0;
+
+ input_combo.pack_start (midi_port_columns.short_name);
+ output_combo.pack_start (midi_port_columns.short_name);
+
+ input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::active_port_changed), &input_combo, true));
+ output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::active_port_changed), &output_combo, false));
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
+ l->set_alignment (1.0, 0.5);
+ table.attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ table.attach (input_combo, 2, 6, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
+ l->set_alignment (1.0, 0.5);
+ table.attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ table.attach (output_combo, 2, 6, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+
+ pack_start (table);
+
+ /* actions */
+ build_available_action_menu ();
+
+ int action_row = 0;
+ int action_col = 0;
+ Gtk::Alignment* align;
+
+ for (FP8Controls::UserButtonMap::const_iterator i = fp.control().user_buttons ().begin ();
+ i != fp.control().user_buttons ().end (); ++i) {
+ Gtk::ComboBox* user_combo = manage (new Gtk::ComboBox);
+ build_action_combo (*user_combo, i->first);
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1:</span>", i->second));
+ l->set_alignment (1.0, 0.5);
+ table.attach (*l, 2 * action_col, 2 * action_col + 1, row + action_row, row + action_row + 1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (*user_combo);
+ table.attach (*align, 2 * action_col + 1, 2 * action_col + 2, row + action_row, row + action_row + 1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+
+ if (++action_row == 4) {
+ action_row = 0;
+ ++action_col;
+ }
+ }
+
+ /* update the port connection combos */
+ update_port_combos ();
+
+ /* catch future changes to connection state */
+ fp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&FP8GUI::connection_handler, this), gui_context());
+}
+
+FP8GUI::~FP8GUI ()
+{
+}
+
+void
+FP8GUI::connection_handler ()
+{
+ PBD::Unwinder<bool> ici (ignore_active_change, true);
+ update_port_combos ();
+}
+
+void
+FP8GUI::update_port_combos ()
+{
+ vector<string> midi_inputs;
+ vector<string> midi_outputs;
+
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+ Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+ Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+ bool input_found = false;
+ bool output_found = false;
+ int n;
+
+ input_combo.set_model (input);
+ output_combo.set_model (output);
+
+ Gtk::TreeModel::Children children = input->children();
+ Gtk::TreeModel::Children::iterator i;
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (fp.input_port()->connected_to (port_name)) {
+ input_combo.set_active (n);
+ input_found = true;
+ break;
+ }
+ }
+
+ if (!input_found) {
+ input_combo.set_active (0); /* disconnected */
+ }
+
+ children = output->children();
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (fp.output_port()->connected_to (port_name)) {
+ output_combo.set_active (n);
+ output_found = true;
+ break;
+ }
+ }
+
+ if (!output_found) {
+ output_combo.set_active (0); /* disconnected */
+ }
+}
+
+
+Glib::RefPtr<Gtk::ListStore>
+FP8GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+ Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+ TreeModel::Row row;
+
+ row = *store->append ();
+ row[midi_port_columns.full_name] = string();
+ row[midi_port_columns.short_name] = _("Disconnected");
+
+ for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+ row = *store->append ();
+ row[midi_port_columns.full_name] = *p;
+ std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+ if (pn.empty ()) {
+ pn = (*p).substr ((*p).find (':') + 1);
+ }
+ row[midi_port_columns.short_name] = pn;
+ }
+
+ return store;
+}
+
+void
+FP8GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
+{
+ if (ignore_active_change) {
+ return;
+ }
+
+ TreeModel::iterator active = combo->get_active ();
+ string new_port = (*active)[midi_port_columns.full_name];
+
+ if (new_port.empty()) {
+ if (for_input) {
+ fp.input_port()->disconnect_all ();
+ } else {
+ fp.output_port()->disconnect_all ();
+ }
+
+ return;
+ }
+
+ if (for_input) {
+ if (!fp.input_port()->connected_to (new_port)) {
+ fp.input_port()->disconnect_all ();
+ fp.input_port()->connect (new_port);
+ }
+ } else {
+ if (!fp.output_port()->connected_to (new_port)) {
+ fp.output_port()->disconnect_all ();
+ fp.output_port()->connect (new_port);
+ }
+ }
+}
+
+
+
+void
+FP8GUI::build_available_action_menu ()
+{
+ /* build a model of all available actions (needs to be tree structured
+ * more)
+ */
+
+ available_action_model = TreeStore::create (action_columns);
+
+ vector<string> paths;
+ vector<string> labels;
+ vector<string> tooltips;
+ vector<string> keys;
+ vector<Glib::RefPtr<Gtk::Action> > actions;
+
+ Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
+
+ typedef std::map<string,TreeIter> NodeMap;
+ NodeMap nodes;
+ NodeMap::iterator r;
+
+
+ vector<string>::iterator k;
+ vector<string>::iterator p;
+ vector<string>::iterator t;
+ vector<string>::iterator l;
+
+ available_action_model->clear ();
+
+ TreeIter rowp;
+ TreeModel::Row parent;
+
+ /* Disabled item (row 0) */
+
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[action_columns.name] = _("Disabled");
+
+ for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
+
+ TreeModel::Row row;
+ vector<string> parts;
+
+ parts.clear ();
+
+ split (*p, parts, '/');
+
+ if (parts.empty()) {
+ continue;
+ }
+
+ //kinda kludgy way to avoid displaying menu items as mappable
+ if ( parts[1] == _("Main_menu") )
+ continue;
+ if ( parts[1] == _("JACK") )
+ continue;
+ if ( parts[1] == _("redirectmenu") )
+ continue;
+ if ( parts[1] == _("Editor_menus") )
+ continue;
+ if ( parts[1] == _("RegionList") )
+ continue;
+ if ( parts[1] == _("ProcessorMenu") )
+ continue;
+
+ if ((r = nodes.find (parts[1])) == nodes.end()) {
+
+ /* top level is missing */
+
+ TreeIter rowp;
+ TreeModel::Row parent;
+ rowp = available_action_model->append();
+ nodes[parts[1]] = rowp;
+ parent = *(rowp);
+ parent[action_columns.name] = parts[1];
+
+ row = *(available_action_model->append (parent.children()));
+
+ } else {
+
+ row = *(available_action_model->append ((*r->second)->children()));
+
+ }
+
+ /* add this action */
+
+ if (l->empty ()) {
+ row[action_columns.name] = *t;
+ action_map[*t] = *p;
+ } else {
+ row[action_columns.name] = *l;
+ action_map[*l] = *p;
+ }
+
+ string path = (*p);
+ /* ControlProtocol::access_action() is not interested in the
+ legacy "<Actions>/" prefix part of a path.
+ */
+ path = path.substr (strlen ("<Actions>/"));
+
+ row[action_columns.path] = path;
+ }
+}
+
+bool
+FP8GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const& action_path, TreeModel::iterator* found)
+{
+ TreeModel::Row row = *iter;
+ string path = row[action_columns.path];
+
+ if (path == action_path) {
+ *found = iter;
+ return true;
+ }
+
+ return false;
+}
+
+void
+FP8GUI::build_action_combo (Gtk::ComboBox& cb, FP8Controls::ButtonId id)
+{
+ cb.set_model (available_action_model);
+ cb.pack_start (action_columns.name);
+
+ /* set the active "row" to the right value for the current button binding */
+ string current_action = fp.get_button_action (id, false); /* lookup release action */
+
+ if (current_action.empty()) {
+ cb.set_active (0); /* "disabled" */
+ } else {
+ TreeModel::iterator iter = available_action_model->children().end();
+
+ available_action_model->foreach_iter (sigc::bind (sigc::mem_fun (*this, &FP8GUI::find_action_in_model), current_action, &iter));
+
+ if (iter != available_action_model->children().end()) {
+ cb.set_active (iter);
+ } else {
+ cb.set_active (0);
+ }
+ }
+ /* bind signal _after_ setting the current value */
+ cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::action_changed), &cb, id));
+}
+
+void
+FP8GUI::action_changed (Gtk::ComboBox* cb, FP8Controls::ButtonId id)
+{
+ TreeModel::const_iterator row = cb->get_active ();
+ string action_path = (*row)[action_columns.path];
+ fp.set_button_action (id, false, action_path);
+}
diff --git a/libs/surfaces/faderport8/gui.h b/libs/surfaces/faderport8/gui.h
new file mode 100644
index 0000000000..20b9ebfedf
--- /dev/null
+++ b/libs/surfaces/faderport8/gui.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __ardour_faderport8_gui_h__
+#define __ardour_faderport8_gui_h__
+
+#include <vector>
+#include <string>
+
+#include <gtkmm/box.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/image.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treestore.h>
+
+namespace Gtk {
+ class CellRendererCombo;
+ class ListStore;
+}
+
+#include "faderport8.h"
+
+namespace ArdourSurface {
+
+class FP8GUI : public Gtk::VBox
+{
+public:
+ FP8GUI (FaderPort8&);
+ ~FP8GUI ();
+
+private:
+ FaderPort8& fp;
+ Gtk::Table table;
+ Gtk::Image image;
+
+ /* port connections */
+ Gtk::ComboBox input_combo;
+ Gtk::ComboBox output_combo;
+
+ void update_port_combos ();
+ void connection_handler ();
+ PBD::ScopedConnection connection_change_connection;
+
+ struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+ MidiPortColumns() {
+ add (short_name);
+ add (full_name);
+ }
+ Gtk::TreeModelColumn<std::string> short_name;
+ Gtk::TreeModelColumn<std::string> full_name;
+ };
+
+ MidiPortColumns midi_port_columns;
+ bool ignore_active_change;
+
+ Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+ void active_port_changed (Gtk::ComboBox*,bool for_input);
+
+ /* user actions */
+ void build_available_action_menu ();
+ void build_action_combo (Gtk::ComboBox& cb, FP8Controls::ButtonId id);
+ void action_changed (Gtk::ComboBox* cb, FP8Controls::ButtonId id);
+
+ struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
+ ActionColumns() {
+ add (name);
+ add (path);
+ }
+ Gtk::TreeModelColumn<std::string> name;
+ Gtk::TreeModelColumn<std::string> path;
+ };
+
+ ActionColumns action_columns;
+ Glib::RefPtr<Gtk::TreeStore> available_action_model;
+ std::map<std::string,std::string> action_map; // map from action names to paths
+
+ bool find_action_in_model (const Gtk::TreeModel::iterator& iter, std::string const & action_path, Gtk::TreeModel::iterator* found);
+};
+
+}
+
+#endif /* __ardour_faderport8_gui_h__ */
diff --git a/libs/surfaces/faderport8/wscript b/libs/surfaces/faderport8/wscript
new file mode 100644
index 0000000000..da2fb8e300
--- /dev/null
+++ b/libs/surfaces/faderport8/wscript
@@ -0,0 +1,37 @@
+#!/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 = [
+ 'faderport8.cc',
+ 'faderport8_interface.cc',
+ 'fp8_controls.cc',
+ 'fp8_strip.cc',
+ 'callbacks.cc',
+ 'actions.cc',
+ 'gui.cc'
+ ]
+ obj.export_includes = ['.']
+ obj.defines = [ 'PACKAGE="ardour_faderport8"' ]
+ obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+ obj.includes = [ '.', './faderport8']
+ obj.name = 'libardour_faderport8'
+ obj.target = 'ardour_faderport8'
+ obj.uselib = 'GTKMM GTK GDK XML'
+ obj.use = 'libardour libardour_cp libgtkmm2ext libpbd'
+ obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+ autowaf.shutdown()
diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript
index 7ab04ef9f8..0e34356de1 100644
--- a/libs/surfaces/wscript
+++ b/libs/surfaces/wscript
@@ -22,6 +22,7 @@ out = 'build'
children = [
'control_protocol',
'faderport',
+ 'faderport8',
'cc121',
'generic_midi',
'mackie',
@@ -75,6 +76,7 @@ def build(bld):
bld.recurse('control_protocol')
bld.recurse('generic_midi')
bld.recurse('faderport')
+ bld.recurse('faderport8')
bld.recurse('cc121')
bld.recurse('mackie')