FP8: add mode to reset gain to unity
[ardour.git] / libs / surfaces / faderport8 / faderport8.cc
index 54bbb655d815ad6c83c23aeec7e7d58afd24cb80..2603a12e346b17310caa47d91bd98841b0016402 100644 (file)
 #include "ardour/debug.h"
 #include "ardour/midi_track.h"
 #include "ardour/midiport_manager.h"
+#include "ardour/plugin.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/tempo.h"
 #include "ardour/vca.h"
 
 #include "faderport8.h"
@@ -89,13 +91,20 @@ FaderPort8::FaderPort8 (Session& s)
        , _connection_state (ConnectionState (0))
        , _device_active (false)
        , _ctrls (*this)
-       , _channel_off (0)
        , _plugin_off (0)
        , _parameter_off (0)
+       , _show_presets (false)
+       , _showing_well_known (0)
        , _blink_onoff (false)
        , _shift_lock (false)
-       , _shift_pressed (false)
+       , _shift_pressed (0)
        , gui (0)
+       , _link_enabled (false)
+       , _link_locked (false)
+       , _clock_mode (1)
+       , _scribble_mode (2)
+       , _two_line_text (false)
+       , _auto_pluginui (true)
 {
        boost::shared_ptr<ARDOUR::Port> inp;
        boost::shared_ptr<ARDOUR::Port> outp;
@@ -124,39 +133,44 @@ FaderPort8::FaderPort8 (Session& s)
                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);
+       ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::connection_handler, this, _2, _4), this);
+       ARDOUR::AudioEngine::instance()->Stopped.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::engine_reset, this), this);
+       ARDOUR::Port::PortDrop.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::engine_reset, this), this);
 
+       /* bind button events to call libardour actions */
        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));
+       _ctrls.MixModeChanged.connect_same_thread (modechange_connections, boost::bind (&FaderPort8::assign_strips, this));
 }
 
 FaderPort8::~FaderPort8 ()
 {
-       cerr << "~FP8\n";
-       stop_midi_handling  ();
-       close ();
+       /* this will be called from the main UI thread. during Session::destroy().
+        * There can be concurrent activity from BaseUI::main_thread -> AsyncMIDIPort
+        * -> MIDI::Parser::signal -> ... to any of the midi_connections
+        *
+        * stop event loop early and join thread */
+       stop ();
 
        if (_input_port) {
                DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
+               Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
                AudioEngine::instance()->unregister_port (_input_port);
                _input_port.reset ();
        }
 
+       disconnected (); // zero faders, turn lights off, clear strips
+
        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()));
+               Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
                AudioEngine::instance()->unregister_port (_output_port);
                _output_port.reset ();
        }
 
        tear_down_gui ();
-
-       /* stop event loop */
-       DEBUG_TRACE (DEBUG::FaderPort8, "BaseUI::quit ()\n");
-       BaseUI::quit ();
 }
 
 /* ****************************************************************************
@@ -181,46 +195,50 @@ FaderPort8::do_request (FaderPort8Request* req)
                call_slot (MISSING_INVALIDATOR, req->the_slot);
        } else if (req->type == Quit) {
                stop ();
+               disconnected ();
        }
 }
 
-int
+void
 FaderPort8::stop ()
 {
+       DEBUG_TRACE (DEBUG::FaderPort8, "BaseUI::quit ()\n");
        BaseUI::quit ();
-       return 0;
+       close (); // drop references, disconnect from session signals
 }
 
 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.
-       }
+       set_thread_priority ();
 }
 
 bool
 FaderPort8::periodic ()
 {
-       /* prepare TC display -- handled by stripable Periodic () */
-       if (_ctrls.display_timecode ()) {
-               // TODO allow BBT, HHMMSS
-               // used in FP8Strip::periodic_update_timecode
+       /* prepare TC display -- handled by stripable Periodic ()
+        * in FP8Strip::periodic_update_timecode
+        */
+       if (_ctrls.display_timecode () && clock_mode ()) {
                Timecode::Time TC;
                session->timecode_time (TC);
                _timecode = Timecode::timecode_format_time(TC);
+
+               char buf[16];
+               Timecode::BBT_Time BBT = session->tempo_map ().bbt_at_frame (session->transport_frame ());
+               snprintf (buf, sizeof (buf),
+                               " %02" PRIu32 "|%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32,
+                               BBT.bars % 100, BBT.beats %100,
+                               (BBT.ticks/ 100) %100, BBT.ticks %100);
+               _musical_time = std::string (buf);
        } else {
                _timecode.clear ();
+               _musical_time.clear ();
        }
 
        /* update stripables */
@@ -253,8 +271,7 @@ FaderPort8::set_active (bool yn)
                BaseUI::run ();
                connect_session_signals ();
        } else {
-               BaseUI::quit ();
-               close ();
+               stop ();
        }
 
        ControlProtocol::set_active (yn);
@@ -265,13 +282,14 @@ FaderPort8::set_active (bool yn)
 void
 FaderPort8::close ()
 {
-       _assigned_strips.clear ();
+       DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::close\n");
        stop_midi_handling ();
        session_connections.drop_connections ();
        automation_state_connections.drop_connections ();
        assigned_stripable_connections.drop_connections ();
+       _assigned_strips.clear ();
        drop_ctrl_connections ();
-       port_connection.disconnect ();
+       port_connections.drop_connections ();
        selection_connection.disconnect ();
 }
 
@@ -290,13 +308,20 @@ void
 FaderPort8::connected ()
 {
        DEBUG_TRACE (DEBUG::FaderPort8, "initializing\n");
+       assert (!_device_active);
+
+       if (_device_active) {
+               stop_midi_handling (); // re-init
+       }
+
        // 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;
+       memset (_channel_off, 0, sizeof (_channel_off));
+       _plugin_off = _parameter_off = 0;
        _blink_onoff = false;
        _shift_lock = false;
-       _shift_pressed = false;
+       _shift_pressed = 0;
 
        start_midi_handling ();
        _ctrls.initialize ();
@@ -311,7 +336,7 @@ FaderPort8::connected ()
        tx_midi3 (0x90, 0x46, 0x00);
 
        send_session_state ();
-       assign_strips (true);
+       assign_strips ();
 
        Glib::RefPtr<Glib::TimeoutSource> blink_timer =
                Glib::TimeoutSource::create (200);
@@ -328,13 +353,26 @@ void
 FaderPort8::disconnected ()
 {
        stop_midi_handling ();
-       for (uint8_t id = 0; id < 8; ++id) {
-               _ctrls.strip(id).unset_controllables ();
+       if (_device_active) {
+               for (uint8_t id = 0; id < 8; ++id) {
+                       _ctrls.strip(id).unset_controllables ();
+               }
+               _ctrls.all_lights_off ();
        }
 }
 
+void
+FaderPort8::engine_reset ()
+{
+       /* Port::PortDrop is called when the engine is halted or stopped */
+       DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::engine_reset\n");
+       _connection_state = 0;
+       _device_active = false;
+       disconnected ();
+}
+
 bool
-FaderPort8::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+FaderPort8::connection_handler (std::string name1, std::string name2)
 {
 #ifdef VERBOSE_DEBUG
        DEBUG_TRACE (DEBUG::FaderPort8, "FaderPort8::connection_handler: start\n");
@@ -347,13 +385,21 @@ FaderPort8::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1
        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) {
+               DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("Connection notify %1 and %2\n", name1, name2));
+               if (_input_port->connected ()) {
+                       if (_connection_state & InputConnected) {
+                               return false;
+                       }
                        _connection_state |= InputConnected;
                } else {
                        _connection_state &= ~InputConnected;
                }
        } else if (no == name1 || no == name2) {
-               if (yn) {
+               DEBUG_TRACE (DEBUG::FaderPort8, string_compose ("Connection notify %1 and %2\n", name1, name2));
+               if (_output_port->connected ()) {
+                       if (_connection_state & OutputConnected) {
+                               return false;
+                       }
                        _connection_state |= OutputConnected;
                } else {
                        _connection_state &= ~OutputConnected;
@@ -379,7 +425,9 @@ FaderPort8::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1
 
        } else {
                DEBUG_TRACE (DEBUG::FaderPort8, "Device disconnected (input or output or both) or not yet fully connected\n");
-               disconnected ();
+               if (_device_active) {
+                       disconnected ();
+               }
                _device_active = false;
        }
 
@@ -413,7 +461,7 @@ FaderPort8::midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::A
 {
        boost::shared_ptr<AsyncMIDIPort> port (wport.lock());
 
-       if (!port) {
+       if (!port || !_input_port) {
                return false;
        }
 
@@ -495,7 +543,7 @@ FaderPort8::pitchbend_handler (MIDI::Parser &, uint8_t chan, MIDI::pitchbend_t p
        /* fader 0..16368 (0x3ff0 -- 1024 steps) */
        bool handled = _ctrls.midi_fader (chan, pb);
        /* if Shift key is held while moving a fader (group override), don't lock shift. */
-       if (_shift_pressed && handled) {
+       if ((_shift_pressed > 0) && handled) {
                _shift_connection.disconnect ();
                _shift_lock = false;
        }
@@ -528,7 +576,10 @@ FaderPort8::note_on_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
 
        /* special case shift */
        if (tb->note_number == 0x06 || tb->note_number == 0x46) {
-               _shift_pressed = true;
+               _shift_pressed |= (tb->note_number == 0x06) ? 1 : 2;
+               if (_shift_pressed == 3) {
+                       return;
+               }
                _shift_connection.disconnect ();
                if (_shift_lock) {
                        _shift_lock = false;
@@ -565,7 +616,10 @@ FaderPort8::note_off_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
 
        /* special case shift */
        if (tb->note_number == 0x06 || tb->note_number == 0x46) {
-               _shift_pressed = false;
+               _shift_pressed &= (tb->note_number == 0x06) ? 2 : 1;
+               if (_shift_pressed > 0) {
+                       return;
+               }
                if (_shift_lock) {
                        return;
                }
@@ -580,7 +634,7 @@ FaderPort8::note_off_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
 
        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) {
+       if ((_shift_pressed > 0) && handled) {
                _shift_connection.disconnect ();
                _shift_lock = false;
        }
@@ -647,6 +701,10 @@ FaderPort8::get_state ()
        child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state());
        node.add_child_nocopy (*child);
 
+       node.set_property (X_("clock-mode"), _clock_mode);
+       node.set_property (X_("scribble-mode"), _scribble_mode);
+       node.set_property (X_("two-line-text"), _two_line_text);
+
        for (UserActionMap::const_iterator i = _user_action_map.begin (); i != _user_action_map.end (); ++i) {
                if (i->second.empty()) {
                        continue;
@@ -656,12 +714,12 @@ FaderPort8::get_state ()
                        continue;
                }
                XMLNode* btn = new XMLNode (X_("Button"));
-               btn->add_property (X_("id"), name);
+               btn->set_property (X_("id"), name);
                if (!i->second.action(true).empty ()) {
-                       btn->add_property ("press", i->second.action(true)._action_name);
+                       btn->set_property ("press", i->second.action(true)._action_name);
                }
                if (!i->second.action(false).empty ()) {
-                       btn->add_property ("release", i->second.action(false)._action_name);
+                       btn->set_property ("release", i->second.action(false)._action_name);
                }
                node.add_child_nocopy (*btn);
        }
@@ -697,6 +755,10 @@ FaderPort8::set_state (const XMLNode& node, int version)
                }
        }
 
+       node.get_property (X_("clock-mode"), _clock_mode);
+       node.get_property (X_("scribble-mode"), _scribble_mode);
+       node.get_property (X_("two-line-text"), _two_line_text);
+
        _user_action_map.clear ();
        // TODO: When re-loading state w/o surface re-init becomes possible,
        // unset lights and reset colors of user buttons.
@@ -705,23 +767,23 @@ FaderPort8::set_state (const XMLNode& node, int version)
                if ((*n)->name() != X_("Button")) {
                        continue;
                }
-               XMLProperty const* prop = (*n)->property (X_("id"));
-               if (!prop) {
+
+               std::string id_str;
+               if (!(*n)->get_property (X_("id"), id_str)) {
                        continue;
                }
 
                FP8Controls::ButtonId id;
-               if (!_ctrls.button_name_to_enum (prop->value(), id)) {
+               if (!_ctrls.button_name_to_enum (id_str, id)) {
                        continue;
                }
 
-               prop = (*n)->property (X_("press"));
-               if (prop) {
-                       set_button_action (id, true, prop->value());
+               std::string action_str;
+               if ((*n)->get_property (X_("press"), action_str)) {
+                       set_button_action (id, true, action_str);
                }
-               prop = (*n)->property (X_("release"));
-               if (prop) {
-                       set_button_action (id, false, prop->value());
+               if ((*n)->get_property (X_("release"), action_str)) {
+                       set_button_action (id, false, action_str);
                }
        }
 
@@ -796,52 +858,6 @@ static bool flt_instrument (boost::shared_ptr<Stripable> s) {
        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_b) {
-                       return a->presentation_info().order() < b->presentation_info().order();
-               }
-#endif
-               return cmp_a < cmp_b;
-       }
-};
-
 void
 FaderPort8::filter_stripables (StripableList& strips) const
 {
@@ -882,6 +898,9 @@ FaderPort8::filter_stripables (StripableList& strips) const
                case MixFX:
                        flt = &flt_auxbus;
                        break;
+               default:
+                       assert (0);
+                       // fall through
                case MixAll:
                        allow_master = true;
                        flt = &flt_all;
@@ -902,10 +921,10 @@ FaderPort8::filter_stripables (StripableList& strips) const
                        strips.push_back (*s);
                }
        }
-       strips.sort (FP8SortByNewDisplayOrder());
+       strips.sort (Stripable::Sorter(true));
 }
 
-/* Track/Pan mode: assign stripable to strips */
+/* Track/Pan mode: assign stripable to strips, Send-mode: selection */
 void
 FaderPort8::assign_stripables (bool select_only)
 {
@@ -917,11 +936,13 @@ FaderPort8::assign_stripables (bool select_only)
        }
 
        int n_strips = strips.size();
-       _channel_off = std::min (_channel_off, n_strips - 8);
-       _channel_off = std::max (0, _channel_off);
+       int channel_off = get_channel_off (_ctrls.mix_mode ());
+       channel_off = std::min (channel_off, n_strips - 8);
+       channel_off = std::max (0, channel_off);
+       set_channel_off (_ctrls.mix_mode (), channel_off);
 
        uint8_t id = 0;
-       int skip = _channel_off;
+       int skip = channel_off;
        for (StripableList::const_iterator s = strips.begin(); s != strips.end(); ++s) {
                if (skip > 0) {
                        --skip;
@@ -938,6 +959,7 @@ FaderPort8::assign_stripables (bool select_only)
                                boost::bind (&FaderPort8::notify_stripable_property_changed, this, boost::weak_ptr<Stripable> (*s), _1), this);
 
                if (select_only) {
+                       /* used in send mode */
                        _ctrls.strip(id).set_text_line (3, (*s)->name (), true);
                        _ctrls.strip(id).select_button ().set_color ((*s)->presentation_info ().color());
                        /* update selection lights */
@@ -956,6 +978,102 @@ FaderPort8::assign_stripables (bool select_only)
        }
        for (; id < 8; ++id) {
                _ctrls.strip(id).unset_controllables (select_only ? (FP8Strip::CTRL_SELECT | FP8Strip::CTRL_TEXT3) : FP8Strip::CTRL_ALL);
+               _ctrls.strip(id).set_periodic_display_mode (FP8Strip::Stripables);
+       }
+}
+
+/* ****************************************************************************
+ * Control Link/Lock
+ */
+
+void
+FaderPort8::unlock_link (bool drop)
+{
+       link_locked_connection.disconnect ();
+
+       if (drop) {
+               stop_link (); // calls back here with drop = false
+               return;
+       }
+
+       _link_locked = false;
+
+       if (_link_enabled) {
+               assert (_ctrls.button (FP8Controls::BtnLink).is_active ());
+               _link_control.reset ();
+               start_link (); // re-connect & update LED colors
+       } else {
+               _ctrls.button (FP8Controls::BtnLink).set_active (false);
+               _ctrls.button (FP8Controls::BtnLink).set_color (0x888888ff);
+               _ctrls.button (FP8Controls::BtnLock).set_active (false);
+               _ctrls.button (FP8Controls::BtnLock).set_color (0x888888ff);
+       }
+}
+
+void
+FaderPort8::lock_link ()
+{
+       boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (_link_control.lock ());
+       if (!ac) {
+               return;
+       }
+       ac->DropReferences.connect (link_locked_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort8::unlock_link, this, true), this);
+
+       // stop watching for focus events
+       link_connection.disconnect ();
+
+       _link_locked = true;
+
+       _ctrls.button (FP8Controls::BtnLock).set_color (0x00ff00ff);
+       _ctrls.button (FP8Controls::BtnLink).set_color (0x00ff00ff);
+}
+
+void
+FaderPort8::stop_link ()
+{
+       if (!_link_enabled) {
+               return;
+       }
+       link_connection.disconnect ();
+       _link_control.reset ();
+       _link_enabled = false;
+       unlock_link (); // also updates button colors
+}
+
+void
+FaderPort8::start_link ()
+{
+       assert (!_link_locked);
+
+       _link_enabled = true;
+       _ctrls.button (FP8Controls::BtnLink).set_active (true);
+       _ctrls.button (FP8Controls::BtnLock).set_active (true);
+       nofity_focus_control (_link_control); // update BtnLink, BtnLock colors
+
+       PBD::Controllable::GUIFocusChanged.connect (link_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort8::nofity_focus_control, this, _1), this);
+}
+
+
+/* ****************************************************************************
+ * Plugin selection and parameters
+ */
+
+void
+FaderPort8::toggle_preset_param_mode ()
+{
+       FaderMode fadermode = _ctrls.fader_mode ();
+       if (fadermode != ModePlugins || _proc_params.size() == 0) {
+               return;
+       }
+       _show_presets = ! _show_presets;
+       assign_processor_ctrls ();
+}
+
+void
+FaderPort8::preset_changed ()
+{
+       if (_show_presets) {
+               assign_processor_ctrls ();
        }
 }
 
@@ -968,6 +1086,13 @@ FaderPort8::assign_processor_ctrls ()
        }
        set_periodic_display_mode (FP8Strip::PluginParam);
 
+       if (_show_presets) {
+               if (assign_plugin_presets (_plugin_insert.lock ())) {
+                       return;
+               }
+               _show_presets = false;
+       }
+
        std::vector <ProcessorCtrl*> toggle_params;
        std::vector <ProcessorCtrl*> slider_params;
 
@@ -987,25 +1112,27 @@ FaderPort8::assign_processor_ctrls ()
        uint8_t id = 0;
        for (size_t i = _parameter_off; i < (size_t)n_parameters; ++i) {
                if (i >= toggle_params.size ()) {
-                       _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT0 & ~FP8Strip::CTRL_TEXT1);
+                       _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT01 & ~FP8Strip::CTRL_TEXT2);
                }
                else if (i >= slider_params.size ()) {
                        _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_SELECT & ~FP8Strip::CTRL_TEXT3);
                } else {
-                       _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT0 & ~FP8Strip::CTRL_TEXT1 & ~FP8Strip::CTRL_SELECT & ~FP8Strip::CTRL_TEXT3);
+                       _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT & ~FP8Strip::CTRL_SELECT);
                }
 
                if (i < slider_params.size ()) {
                        _ctrls.strip(id).set_fader_controllable (slider_params[i]->ac);
-                       _ctrls.strip(id).set_text_line (0, slider_params[i]->name);
+                       std::string param_name = slider_params[i]->name;
+                       _ctrls.strip(id).set_text_line (0, param_name.substr (0, 9));
+                       _ctrls.strip(id).set_text_line (1, param_name.length () > 9 ? param_name.substr (9) : "");
                }
                if (i < toggle_params.size ()) {
                        _ctrls.strip(id).set_select_controllable (toggle_params[i]->ac);
                        _ctrls.strip(id).set_text_line (3, toggle_params[i]->name, true);
                }
-                if (++id == 8) {
-                        break;
-                }
+               if (++id == 8) {
+                       break;
+               }
        }
 
        // clear remaining
@@ -1014,6 +1141,65 @@ FaderPort8::assign_processor_ctrls ()
        }
 }
 
+bool
+FaderPort8::assign_plugin_presets (boost::shared_ptr<PluginInsert> pi)
+{
+       if (!pi) {
+               return false;
+       }
+       boost::shared_ptr<ARDOUR::Plugin> plugin = pi->plugin ();
+
+       std::vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets ();
+       if (presets.size () == 0) {
+               return false;
+       }
+
+       int n_parameters = presets.size ();
+
+       _parameter_off = std::min (_parameter_off, n_parameters - 7);
+       _parameter_off = std::max (0, _parameter_off);
+       Plugin::PresetRecord active = plugin->last_preset ();
+
+       uint8_t id = 0;
+       for (size_t i = _parameter_off; i < (size_t)n_parameters; ++i) {
+               _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT01 & ~FP8Strip::CTRL_TEXT3 & ~FP8Strip::CTRL_SELECT);
+                boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin_preset, this, i));
+               _ctrls.strip(id).set_select_cb (cb);
+               _ctrls.strip(id).select_button ().set_active (true);
+               if (active != presets.at(i)) {
+                       _ctrls.strip(id).select_button ().set_color (0x0000ffff);
+                       _ctrls.strip(id).select_button ().set_blinking (false);
+               } else {
+                       _ctrls.strip(id).select_button ().set_color (0x00ffffff);
+                       _ctrls.strip(id).select_button ().set_blinking (plugin->parameter_changed_since_last_preset ());
+               }
+               std::string label = presets.at(i).label;
+               _ctrls.strip(id).set_text_line (0, label.substr (0, 9));
+               _ctrls.strip(id).set_text_line (1, label.length () > 9 ? label.substr (9) : "");
+               _ctrls.strip(id).set_text_line (3, "PRESET", true);
+               if (++id == 7) {
+                       break;
+               }
+       }
+
+       // clear remaining
+       for (; id < 7; ++id) {
+               _ctrls.strip(id).unset_controllables ();
+       }
+
+       // pin clear-preset to the last slot
+       assert (id == 7);
+       _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT0 & ~FP8Strip::CTRL_TEXT3 & ~FP8Strip::CTRL_SELECT);
+        boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin_preset, this, SIZE_MAX));
+       _ctrls.strip(id).set_select_cb (cb);
+       _ctrls.strip(id).select_button ().set_blinking (false);
+       _ctrls.strip(id).select_button ().set_color (active.uri.empty() ? 0x00ffffff : 0x0000ffff);
+       _ctrls.strip(id).select_button ().set_active (true);
+       _ctrls.strip(id).set_text_line (0, _("(none)"));
+       _ctrls.strip(id).set_text_line (3, "PRESET", true);
+       return true;
+}
+
 void
 FaderPort8::build_well_known_processor_ctrls (boost::shared_ptr<Stripable> s, bool eq)
 {
@@ -1022,8 +1208,17 @@ FaderPort8::build_well_known_processor_ctrls (boost::shared_ptr<Stripable> s, bo
        _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 ());
+
+#ifdef MIXBUS32C
+               PUSH_BACK_NON_NULL ("Flt In", s->filter_enable_controllable (true)); // both HP/LP
+               PUSH_BACK_NON_NULL ("HP Freq", s->filter_freq_controllable (true));
+               PUSH_BACK_NON_NULL ("LP Freq", s->filter_freq_controllable (false));
+               PUSH_BACK_NON_NULL ("EQ In", s->eq_enable_controllable ());
+#elif defined (MIXBUS)
+               PUSH_BACK_NON_NULL ("EQ In", s->eq_enable_controllable ());
+               PUSH_BACK_NON_NULL ("HP Freq", s->filter_freq_controllable (true));
+#endif
+
                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));
@@ -1032,7 +1227,7 @@ FaderPort8::build_well_known_processor_ctrls (boost::shared_ptr<Stripable> s, bo
                        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 ("Comp In", 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 ());
@@ -1043,13 +1238,30 @@ void
 FaderPort8::select_plugin (int num)
 {
        // make sure drop_ctrl_connections() was called
-       assert (_proc_params.size() == 0 && _showing_well_known == 0);
+       assert (_proc_params.size() == 0 && _showing_well_known == 0 && _plugin_insert.expired());
 
        boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (first_selected_stripable());
        if (!r) {
                _ctrls.set_fader_mode (ModeTrack);
                return;
        }
+
+       // Toggle Bypass
+       if (shift_mod ()) {
+               if (num >= 0) {
+                       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (r->nth_plugin (num));
+#ifdef MIXBUS
+                       if (pi && !pi->is_channelstrip () && pi->display_to_user ())
+#else
+                       if (pi && pi->display_to_user ())
+#endif
+                       {
+                               pi->enable (! pi->enabled ());
+                       }
+               }
+               return;
+       }
+
        if (num < 0) {
                build_well_known_processor_ctrls (r, num == -1);
                assign_processor_ctrls ();
@@ -1064,6 +1276,33 @@ FaderPort8::select_plugin (int num)
                return;
        }
 
+       // disconnect signals from spill_plugins: processors_changed and ActiveChanged
+       processor_connections.drop_connections ();
+       r->DropReferences.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FP8Controls::set_fader_mode, &_ctrls, ModeTrack), this);
+
+       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
+       assert (pi); // nth_plugin() always returns a PI.
+       /* _plugin_insert is used for Bypass/Enable & presets */
+#ifdef MIXBUS
+       if (!pi->is_channelstrip () && pi->display_to_user ())
+#else
+       if (pi->display_to_user ())
+#endif
+       {
+               _plugin_insert = boost::weak_ptr<ARDOUR::PluginInsert> (pi);
+               pi->ActiveChanged.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_plugin_active_changed, this), this);
+               boost::shared_ptr<ARDOUR::Plugin> plugin = pi->plugin ();
+
+               plugin->PresetAdded.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::preset_changed, this), this);
+               plugin->PresetRemoved.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::preset_changed, this), this);
+               plugin->PresetLoaded.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::preset_changed, this), this);
+               plugin->PresetDirty.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::preset_changed, this), this);
+
+               if (_auto_pluginui) {
+                       pi->ShowUI (); /* EMIT SIGNAL */
+               }
+       }
+
        // 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);
@@ -1083,6 +1322,28 @@ FaderPort8::select_plugin (int num)
 
        // display
        assign_processor_ctrls ();
+       notify_plugin_active_changed ();
+}
+
+void
+FaderPort8::select_plugin_preset (size_t num)
+{
+       assert (_proc_params.size() > 0);
+       boost::shared_ptr<PluginInsert> pi = _plugin_insert.lock();
+       if (!pi) {
+               _ctrls.set_fader_mode (ModeTrack);
+               return;
+       }
+       if (num == SIZE_MAX) {
+               pi->plugin ()->clear_preset ();
+       } else {
+               std::vector<ARDOUR::Plugin::PresetRecord> presets = pi->plugin ()->get_presets ();
+               if (num < presets.size ()) {
+                       pi->load_preset (presets.at (num));
+               }
+       }
+       _show_presets = false;
+       assign_processor_ctrls ();
 }
 
 /* short 4 chars at most */
@@ -1131,12 +1392,16 @@ FaderPort8::spill_plugins ()
 
        for (uint32_t i = 0; 0 != (proc = r->nth_plugin (i)); ++i) {
                if (!proc->display_to_user ()) {
+#ifdef MIXBUS
+                       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
+                       if (pi->is_channelstrip ()) // don't skip MB PRE
+#endif
                        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);
+               for (set<Evoral::Parameter>::iterator j = p.begin(); j != p.end(); ++j) {
+                       std::string n = proc->describe_parameter (*j);
                        if (n == "hidden") {
                                continue;
                        }
@@ -1182,18 +1447,20 @@ FaderPort8::spill_plugins ()
                        break;
                }
                boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
-               boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin, this, i));
+               boost::function<void ()> cb (boost::bind (&FaderPort8::select_plugin, this, procs[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_color (proc->enabled () ? 0x00ff00ff : 0xff0000ff);
+               _ctrls.strip(id).select_button ().set_active (true);
                _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()));
                _ctrls.strip(id).set_text_line (3, "");
 
+               pi->ActiveChanged.connect (processor_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::spill_plugins, this), this);
+
                if (++id == spillwidth) {
                        break;
                }
@@ -1234,6 +1501,10 @@ FaderPort8::spill_plugins ()
        assert (id == 8);
 }
 
+/* ****************************************************************************
+ * Aux Sends and Mixbus assigns
+ */
+
 void
 FaderPort8::assign_sends ()
 {
@@ -1272,11 +1543,10 @@ FaderPort8::assign_sends ()
                        break;
                }
 
-               _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT0 & ~FP8Strip::CTRL_TEXT1 & ~FP8Strip::CTRL_TEXT3 & ~FP8Strip::CTRL_SELECT);
+               _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_FADER & ~FP8Strip::CTRL_TEXT01 & ~FP8Strip::CTRL_TEXT3 & ~FP8Strip::CTRL_SELECT);
                _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));
-               _ctrls.strip(id).set_solo_controllable (s->master_send_enable_controllable ()); // XXX
 
                if (++id == 8) {
                        break;
@@ -1286,34 +1556,31 @@ FaderPort8::assign_sends ()
        for (; id < 8; ++id) {
                _ctrls.strip(id).unset_controllables (FP8Strip::CTRL_ALL & ~FP8Strip::CTRL_TEXT3 & ~FP8Strip::CTRL_SELECT);
        }
+#ifdef MIXBUS // master-assign on last solo
+       _ctrls.strip(7).set_solo_controllable (s->master_send_enable_controllable ());
+#endif
        /* set select buttons */
+       assigned_stripable_connections.drop_connections ();
+       _assigned_strips.clear ();
        assign_stripables (true);
 }
 
-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);
-       }
-}
+/* ****************************************************************************
+ * Main stripable assignment (dispatch depending on mode)
+ */
 
 void
-FaderPort8::assign_strips (bool reset_bank)
+FaderPort8::assign_strips ()
 {
-       if (reset_bank) {
-               _channel_off = 0;
-       }
-
-       _assigned_strips.clear ();
        assigned_stripable_connections.drop_connections ();
+       _assigned_strips.clear ();
 
        FaderMode fadermode = _ctrls.fader_mode ();
        switch (fadermode) {
                case ModeTrack:
                case ModePan:
                        assign_stripables ();
-                       gui_track_selection_changed (); // update selection, automation-state
+                       stripable_selection_changed (); // update selection, automation-state
                        break;
                case ModePlugins:
                        if (_proc_params.size() > 0) {
@@ -1328,15 +1595,87 @@ FaderPort8::assign_strips (bool reset_bank)
        }
 }
 
+/* ****************************************************************************
+ * some helper functions
+ */
+
+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::drop_ctrl_connections ()
 {
        _proc_params.clear();
+       if (_auto_pluginui) {
+               boost::shared_ptr<PluginInsert> pi = _plugin_insert.lock ();
+               if (pi) {
+                       pi->HideUI (); /* EMIT SIGNAL */
+               }
+       }
+       _plugin_insert.reset ();
+       _show_presets = false;
        processor_connections.drop_connections ();
        _showing_well_known = 0;
+       notify_plugin_active_changed ();
 }
 
+/* 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 1 /* single exclusive selection by default, toggle via shift */
+
+# if 1 /* selecting a selected strip -> move fader to unity */
+       if (s == first_selected_stripable () && !shift_mod ()) {
+               if (_ctrls.fader_mode () == ModeTrack) {
+                       boost::shared_ptr<AutomationControl> ac = s->gain_control ();
+                       ac->start_touch (ac->session().transport_frame());
+                       ac->set_value (ac->normal (), PBD::Controllable::UseGroup);
+               }
+               return;
+       }
+# endif
+
+       if (shift_mod ()) {
+               ToggleStripableSelection (s);
+       } else {
+               SetStripableSelection (s);
+       }
+#else
+       /* tri-state selection: This allows to set the "first selected"
+        * with a single click without clearing the selection.
+        * Single de/select via shift.
+        */
+       if (shift_mod ()) {
+               if (s->is_selected ()) {
+                       RemoveStripableFromSelection (s);
+               } else {
+                       SetStripableSelection (s);
+               }
+               return;
+       }
+       if (s->is_selected () && s != first_selected_stripable ()) {
+               set_first_selected_stripable (s);
+               stripable_selection_changed ();
+       } else {
+               ToggleStripableSelection (s);
+       }
+#endif
+}
+
+/* ****************************************************************************
+ * Assigned Stripable Callbacks
+ */
+
 void
 FaderPort8::notify_fader_mode_changed ()
 {
@@ -1358,19 +1697,16 @@ FaderPort8::notify_fader_mode_changed ()
                case ModeSend:
                        _plugin_off = 0;
                        _parameter_off = 0;
+                       stop_link ();
                        // force unset rec-arm button, see also FaderPort8::button_arm
                        _ctrls.button (FP8Controls::BtnArm).set_active (false);
                        ARMButtonChange (false);
                        break;
        }
-       assign_strips (false);
+       assign_strips ();
        notify_automation_mode_changed ();
 }
 
-/* ****************************************************************************
- * Assigned Stripable Callbacks
- */
-
 void
 FaderPort8::notify_stripable_added_or_removed ()
 {
@@ -1381,31 +1717,7 @@ FaderPort8::notify_stripable_added_or_removed ()
         *    - Properties::hidden
         *    - Properties::order
         */
-       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_mod ()) {
-               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);
-       }
+       assign_strips ();
 }
 
 /* called from static PresentationInfo::Change */
@@ -1429,7 +1741,19 @@ FaderPort8::notify_stripable_property_changed (boost::weak_ptr<Stripable> ws, co
                assert (0); // this should not happen
                return;
        }
-       assert (_assigned_strips.find (s) != _assigned_strips.end());
+       if (_assigned_strips.find (s) == _assigned_strips.end()) {
+               /* it can happen that signal emission is delayed.
+                * A signal may already be in the queue but the
+                * _assigned_strips has meanwhile changed.
+                *
+                * before _assigned_strips changes, the connections are dropped
+                * but that does not seem to invalidate pending requests :(
+                *
+                * Seen when creating a new MB session and Mixbusses are added
+                * incrementally.
+                */
+               return;
+       }
        uint8_t id = _assigned_strips[s];
 
        if (what_changed.contains (Properties::color)) {
@@ -1439,10 +1763,11 @@ FaderPort8::notify_stripable_property_changed (boost::weak_ptr<Stripable> ws, co
        if (what_changed.contains (Properties::name)) {
                switch (_ctrls.fader_mode ()) {
                        case ModeSend:
-                               _ctrls.strip(id).set_text_line (0, s->name());
+                               _ctrls.strip(id).set_text_line (3, s->name(), true);
+                               break;
                        case ModeTrack:
                        case ModePan:
-                               _ctrls.strip(id).set_text_line (3, s->name(), true);
+                               _ctrls.strip(id).set_text_line (0, s->name());
                                break;
                        case ModePlugins:
                                assert (0);
@@ -1452,8 +1777,14 @@ FaderPort8::notify_stripable_property_changed (boost::weak_ptr<Stripable> ws, co
 }
 
 void
-FaderPort8::gui_track_selection_changed (/*ARDOUR::StripableNotificationListPtr*/)
+FaderPort8::stripable_selection_changed ()
 {
+       if (!_device_active) {
+               /* this can be called anytime from the static
+                * ControlProtocol::StripableSelectionChanged
+                */
+               return;
+       }
        automation_state_connections.drop_connections();
 
        switch (_ctrls.fader_mode ()) {
@@ -1522,16 +1853,18 @@ FaderPort8::move_selected_into_view ()
        }
        int off = std::distance (strips.begin(), it);
 
-       if (_channel_off <= off && off < _channel_off + 8) {
+       int channel_off = get_channel_off (_ctrls.mix_mode ());
+       if (channel_off <= off && off < channel_off + 8) {
                return;
        }
 
-       if (_channel_off > off) {
-               _channel_off = off;
+       if (channel_off > off) {
+               channel_off = off;
        } else {
-               _channel_off = off - 7;
+               channel_off = off - 7;
        }
-       assign_strips (false);
+       set_channel_off (_ctrls.mix_mode (), channel_off);
+       assign_strips ();
 }
 
 void
@@ -1584,8 +1917,8 @@ FaderPort8::bank (bool down, bool page)
        if (down) {
                dt *= -1;
        }
-       _channel_off += dt;
-       assign_strips (false);
+       set_channel_off (_ctrls.mix_mode (), get_channel_off (_ctrls.mix_mode ()) + dt);
+       assign_strips ();
 }
 
 void
@@ -1595,7 +1928,6 @@ FaderPort8::bank_param (bool down, bool page)
        if (down) {
                dt *= -1;
        }
-       _channel_off += dt;
        switch (_ctrls.fader_mode ()) {
                case ModePlugins:
                        if (_proc_params.size() > 0) {
@@ -1607,6 +1939,7 @@ FaderPort8::bank_param (bool down, bool page)
                        }
                        break;
                case ModeSend:
+                       _plugin_off += dt;
                        assign_sends ();
                        break;
                default: