FP8: add mode to reset gain to unity
[ardour.git] / libs / surfaces / faderport8 / faderport8.cc
index 42c6c99b36645c047b525c7841e4d53192d816d9..2603a12e346b17310caa47d91bd98841b0016402 100644 (file)
@@ -44,6 +44,7 @@
 #include "ardour/route.h"
 #include "ardour/session.h"
 #include "ardour/session_configuration.h"
+#include "ardour/tempo.h"
 #include "ardour/vca.h"
 
 #include "faderport8.h"
@@ -90,7 +91,6 @@ 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)
@@ -99,6 +99,12 @@ FaderPort8::FaderPort8 (Session& s)
        , _shift_lock (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;
@@ -135,33 +141,36 @@ FaderPort8::FaderPort8 (Session& s)
        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";
-       disconnected ();
-       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 ();
 }
 
 /* ****************************************************************************
@@ -186,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 */
@@ -258,8 +271,7 @@ FaderPort8::set_active (bool yn)
                BaseUI::run ();
                connect_session_signals ();
        } else {
-               BaseUI::quit ();
-               close ();
+               stop ();
        }
 
        ControlProtocol::set_active (yn);
@@ -305,7 +317,8 @@ FaderPort8::connected ()
        // 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 = 0;
@@ -323,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);
@@ -448,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;
        }
 
@@ -688,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;
@@ -738,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.
@@ -900,10 +921,10 @@ FaderPort8::filter_stripables (StripableList& strips) const
                        strips.push_back (*s);
                }
        }
-       strips.sort (Stripable::Sorter());
+       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)
 {
@@ -915,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;
@@ -936,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 */
@@ -954,9 +978,82 @@ 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
  */
@@ -1200,6 +1297,10 @@ FaderPort8::select_plugin (int num)
                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()
@@ -1469,12 +1570,8 @@ FaderPort8::assign_sends ()
  */
 
 void
-FaderPort8::assign_strips (bool reset_bank)
+FaderPort8::assign_strips ()
 {
-       if (reset_bank) {
-               _channel_off = 0;
-       }
-
        assigned_stripable_connections.drop_connections ();
        _assigned_strips.clear ();
 
@@ -1514,6 +1611,12 @@ 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 ();
@@ -1530,6 +1633,18 @@ FaderPort8::select_strip (boost::weak_ptr<Stripable> ws)
                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 {
@@ -1582,12 +1697,13 @@ 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 ();
 }
 
@@ -1601,7 +1717,7 @@ FaderPort8::notify_stripable_added_or_removed ()
         *    - Properties::hidden
         *    - Properties::order
         */
-       assign_strips (false);
+       assign_strips ();
 }
 
 /* called from static PresentationInfo::Change */
@@ -1737,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
@@ -1799,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