Flush processor (re-activate) on route active change
[ardour.git] / libs / ardour / slavable_automation_control.cc
index 624fa484caf973d943e016ba27091f650172bd6b..63784dc3a5bd52b947771d18f8941499af41a507 100644 (file)
 #define __libardour_slavable_automation_control_h__
 
 #include "pbd/enumwriter.h"
+#include "pbd/error.h"
+#include "pbd/i18n.h"
 
-#include "ardour/automation_control.h"
+#include "ardour/audioengine.h"
+#include "ardour/slavable_automation_control.h"
 #include "ardour/session.h"
 
 using namespace std;
@@ -32,15 +35,24 @@ SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
                                                      const Evoral::Parameter&                  parameter,
                                                      const ParameterDescriptor&                desc,
                                                      boost::shared_ptr<ARDOUR::AutomationList> l,
-                                                     const std::string&                        name)
-       : AutomationControl (s, parameter, desc, l, name)
+                                                     const std::string&                        name,
+                                                     Controllable::Flag                        flags)
+       : AutomationControl (s, parameter, desc, l, name, flags)
+       , _masters_node (0)
 {
 }
 
+SlavableAutomationControl::~SlavableAutomationControl ()
+{
+       if (_masters_node) {
+               delete _masters_node;
+               _masters_node = 0;
+       }
+}
+
 double
 SlavableAutomationControl::get_masters_value_locked () const
 {
-       double v = _desc.normal;
 
        if (_desc.toggled) {
                for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
@@ -49,14 +61,16 @@ SlavableAutomationControl::get_masters_value_locked () const
                        }
                }
                return _desc.lower;
-       }
+       } else {
 
-       for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
-               /* get current master value, scale by our current ratio with that master */
-               v *= mr->second.master()->get_value () * mr->second.ratio();
-       }
+               double v = 1.0; /* the masters function as a scaling factor */
 
-       return min ((double) _desc.upper, v);
+               for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                       v *= mr->second.master()->get_value ();
+               }
+
+               return v;
+       }
 }
 
 double
@@ -78,7 +92,7 @@ SlavableAutomationControl::get_value_locked() const
                }
        }
 
-       return get_masters_value_locked ();
+       return Control::get_double() * get_masters_value_locked ();
 }
 
 /** Get the current effective `user' value based on automation state */
@@ -87,54 +101,65 @@ SlavableAutomationControl::get_value() const
 {
        bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
 
+       Glib::Threads::RWLock::ReaderLock lm (master_lock);
        if (!from_list) {
-               Glib::Threads::RWLock::ReaderLock lm (master_lock);
                return get_value_locked ();
        } else {
-               return Control::get_double (from_list, _session.transport_frame());
+               return Control::get_double (true, _session.transport_frame()) * get_masters_value_locked();
        }
 }
 
 void
-SlavableAutomationControl::actually_set_value (double val, Controllable::GroupControlDisposition group_override)
+SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
 {
-       val = std::max (std::min (val, (double)_desc.upper), (double)_desc.lower);
+       if (!_desc.toggled) {
 
-       {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
 
                if (!_masters.empty()) {
-                       recompute_masters_ratios (val);
+                       /* need to scale given value by current master's scaling */
+                       const double masters_value = get_masters_value_locked();
+                       if (masters_value == 0.0) {
+                               value = 0.0;
+                       } else {
+                               value /= masters_value;
+                               value = std::max (lower(), std::min(upper(), value));
+                       }
                }
        }
 
-       /* this sets the Evoral::Control::_user_value for us, which will
-          be retrieved by AutomationControl::get_value ()
-       */
-       AutomationControl::actually_set_value (val, group_override);
-
-       _session.set_dirty ();
+       /* this will call Control::set_double() and emit Changed signals as appropriate */
+       AutomationControl::actually_set_value (value, gcd);
 }
 
 void
-SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
+SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m, bool loading)
 {
-       double current_value;
-       double new_value;
        std::pair<Masters::iterator,bool> res;
 
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
-               current_value = get_value_locked ();
 
-               /* ratio will be recomputed below */
-
-               res = _masters.insert (make_pair<PBD::ID,MasterRecord> (m->id(), MasterRecord (m, 1.0)));
+               pair<PBD::ID,MasterRecord> newpair (m->id(), MasterRecord (m, 1.0));
+               res = _masters.insert (newpair);
 
                if (res.second) {
 
-                       if (_desc.toggled) {
-                               recompute_masters_ratios (current_value);
+                       if (!loading) {
+
+                               if (!_desc.toggled) {
+                                       const double master_value = m->get_value();
+
+                                       if (master_value == 0.0) {
+                                               AutomationControl::set_double (0.0, Controllable::NoGroup);
+                                       } else {
+                                               /* scale control's own value by
+                                                  amount that the master will
+                                                  contribute.
+                                               */
+                                               AutomationControl::set_double ((Control::get_double() / master_value), Controllable::NoGroup);
+                                       }
+                               }
                        }
 
                        /* note that we bind @param m as a weak_ptr<AutomationControl>, thus
@@ -142,21 +167,26 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
                           itself.
                        */
 
-                       m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, m));
+                       m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr<AutomationControl>(m)));
+
+                       /* Store the connection inside the MasterRecord, so
+                          that when we destroy it, the connection is destroyed
+                          and we no longer hear about changes to the
+                          AutomationControl.
 
-                       /* Store the connection inside the MasterRecord, so that when we destroy it, the connection is destroyed
-                          and we no longer hear about changes to the AutomationControl.
+                          Note that this also makes it safe to store a
+                          boost::shared_ptr<AutomationControl> in the functor,
+                          since we know we will destroy the functor when the
+                          connection is destroyed, which happens when we
+                          disconnect from the master (for any reason).
 
                           Note that we fix the "from_self" argument that will
                           be given to our own Changed signal to "false",
                           because the change came from the master.
                        */
 
-                       m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2));
-                       cerr << this << enum_2_string ((AutomationType) _parameter.type()) << " now listening to Changed from " << m << endl;
+                       m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2, m));
                }
-
-               new_value = get_value_locked ();
        }
 
        if (res.second) {
@@ -164,30 +194,68 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
                MasterStatusChange (); /* EMIT SIGNAL */
        }
 
-       if (new_value != current_value) {
-               /* need to do this without a writable() check in case
-                * the master is removed while this control is doing
-                * automation playback.
-                */
-                actually_set_value (new_value, Controllable::NoGroup);
+       post_add_master (m);
+
+       update_boolean_masters_records (m);
+}
+
+int32_t
+SlavableAutomationControl::get_boolean_masters () const
+{
+       int32_t n = 0;
+
+       if (_desc.toggled) {
+               Glib::Threads::RWLock::ReaderLock lm (master_lock);
+               for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                       if (mr->second.yn()) {
+                               ++n;
+                       }
+               }
        }
 
+       return n;
 }
 
 void
-SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd)
+SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr<AutomationControl> m)
 {
-       /* our value has (likely) changed, but not because we were
-        * modified. Just the master.
-        */
-
-       /* propagate master state into our own control so that if we stop
-        * being slaved, our value doesn't change, and propagate to any
-        * group this control is part of.
-        */
+       if (_desc.toggled) {
+               /* We may modify a MasterRecord, but we not modify the master
+                * map, so we use a ReaderLock
+                */
+               Glib::Threads::RWLock::ReaderLock lm (master_lock);
+               Masters::iterator mi = _masters.find (m->id());
+               if (mi != _masters.end()) {
+                       /* update MasterRecord to show whether the master is
+                          on/off. We need to store this because the master
+                          may change (in the sense of emitting Changed())
+                          several times without actually changing the result
+                          of ::get_value(). This is a feature of
+                          AutomationControls (or even just Controllables,
+                          really) which have more than a simple scalar
+                          value. For example, the master may be a mute control
+                          which can be muted_by_self() and/or
+                          muted_by_masters(). When either of those two
+                          conditions changes, Changed() will be emitted, even
+                          though ::get_value() will return the same value each
+                          time (1.0 if either are true, 0.0 if neither is).
+
+                          This provides a way for derived types to check
+                          the last known state of a Master when the Master
+                          changes. We update it after calling
+                          ::master_changed() (though derived types must do
+                          this themselves).
+                       */
+                       mi->second.set_yn (m->get_value());
+               }
+       }
+}
 
-       cerr << this << ' ' << enum_2_string ((AutomationType) _parameter.type()) << " pass along " << get_masters_value() << " from master to group\n";
-       actually_set_value (get_masters_value(), Controllable::UseGroup);
+void
+SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::shared_ptr<AutomationControl> m)
+{
+       update_boolean_masters_records (m);
+       Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
 }
 
 void
@@ -202,34 +270,44 @@ SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl>
 void
 SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
 {
-       double current_value;
-       double new_value;
-       bool masters_left;
-       Masters::size_type erased = 0;
+       pre_remove_master (m);
 
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
-               current_value = get_value_locked ();
-               erased = _masters.erase (m->id());
-               if (erased) {
-                       recompute_masters_ratios (current_value);
+
+               if (!_masters.erase (m->id())) {
+                       return;
                }
-               masters_left = _masters.size ();
-               new_value = get_value_locked ();
-       }
 
-       if (erased) {
-               MasterStatusChange (); /* EMIT SIGNAL */
+               if (!_session.deletion_in_progress()) {
+
+                       const double master_value = m->get_value ();
+
+                       if (master_value == 0.0) {
+                               /* slave would have been set to 0.0 as well,
+                                  so just leave it there, and the user can
+                                  bring it back up. this fits with the
+                                  "removing a VCA does not change the level" rule.
+                               */
+                       } else {
+                               /* bump up the control's own value by the level
+                                  of the master that is being removed.
+                               */
+                               AutomationControl::set_double (AutomationControl::get_double() * master_value, Controllable::NoGroup);
+                       }
+               }
        }
 
-       if (new_value != current_value) {
-               if (masters_left == 0) {
-                       /* no masters left, make sure we keep the same value
-                          that we had before.
-                       */
-                       actually_set_value (current_value, Controllable::UseGroup);
-               }
+       if (_session.deletion_in_progress()) {
+               /* no reason to care about new values or sending signals */
+               return;
        }
+
+       MasterStatusChange (); /* EMIT SIGNAL */
+
+       /* no need to update boolean masters records, since the MR will have
+        * been removed already.
+        */
 }
 
 void
@@ -239,6 +317,9 @@ SlavableAutomationControl::clear_masters ()
        double new_value;
        bool had_masters = false;
 
+       /* null ptr means "all masters */
+       pre_remove_master (boost::shared_ptr<AutomationControl>());
+
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
                current_value = get_value_locked ();
@@ -254,9 +335,12 @@ SlavableAutomationControl::clear_masters ()
        }
 
        if (new_value != current_value) {
-               Changed (false, Controllable::NoGroup);
+               actually_set_value (current_value, Controllable::UseGroup);
        }
 
+       /* no need to update boolean masters records, since all MRs will have
+        * been removed already.
+        */
 }
 
 bool
@@ -273,4 +357,94 @@ SlavableAutomationControl::slaved () const
        return !_masters.empty();
 }
 
+void
+SlavableAutomationControl::use_saved_master_ratios ()
+{
+       if (!_masters_node) {
+               return;
+       }
+
+       Glib::Threads::RWLock::ReaderLock lm (master_lock);
+
+       /* use stored state, do not recompute */
+
+       if (_desc.toggled) {
+
+               XMLNodeList nlist = _masters_node->children();
+               XMLNodeIterator niter;
+
+               for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+                       XMLProperty const * id_prop = (*niter)->property (X_("id"));
+                       if (!id_prop) {
+                               continue;
+                       }
+                       XMLProperty const * yn_prop = (*niter)->property (X_("yn"));
+                       if (!yn_prop) {
+                               continue;
+                       }
+                       Masters::iterator mi = _masters.find (ID (id_prop->value()));
+                       if (mi != _masters.end()) {
+                               mi->second.set_yn (string_is_affirmative (yn_prop->value()));
+                       }
+               }
+
+       } else {
+
+       }
+
+       delete _masters_node;
+       _masters_node = 0;
+
+       return;
+}
+
+
+XMLNode&
+SlavableAutomationControl::get_state ()
+{
+       XMLNode& node (AutomationControl::get_state());
+
+       /* store VCA master ratios */
+
+       {
+               Glib::Threads::RWLock::ReaderLock lm (master_lock);
+
+               if (!_masters.empty()) {
+
+                       XMLNode* masters_node = new XMLNode (X_("masters"));
+
+                       if (_desc.toggled) {
+                               for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                                       XMLNode* mnode = new XMLNode (X_("master"));
+                                       mnode->add_property (X_("id"), mr->second.master()->id().to_s());
+                                       mnode->add_property (X_("yn"), mr->second.yn());
+                                       masters_node->add_child_nocopy (*mnode);
+                               }
+                       } else {
+
+                       }
+
+                       node.add_child_nocopy (*masters_node);
+               }
+       }
+
+       return node;
+}
+
+int
+SlavableAutomationControl::set_state (XMLNode const& node, int version)
+{
+       XMLNodeList nlist = node.children();
+       XMLNodeIterator niter;
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() == X_("masters")) {
+                       _masters_node = new XMLNode (**niter);
+               }
+       }
+
+       return AutomationControl::set_state (node, version);
+}
+
+
 #endif /* __libardour_slavable_automation_control_h__ */