Update Spanish translation
[ardour.git] / libs / ardour / slavable_automation_control.cc
index 07a2d5633a70779b0b15986b3acaf6455b150409..d4002343e881e48909eb37e7c54b0da16e00d4a4 100644 (file)
@@ -19,7 +19,9 @@
 #ifndef __libardour_slavable_automation_control_h__
 #define __libardour_slavable_automation_control_h__
 
-#include "ardour/automation_control.h"
+#include "pbd/enumwriter.h"
+
+#include "ardour/slavable_automation_control.h"
 #include "ardour/session.h"
 
 using namespace std;
@@ -35,29 +37,26 @@ SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
 {
 }
 
-SlavableAutomationControl::~SlavableAutomationControl ()
-{
-
-}
-
 double
 SlavableAutomationControl::get_masters_value_locked () const
 {
-       gain_t v = _desc.normal;
+       double v = _desc.normal;
 
-       for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
-               if (_desc.toggled) {
-                       /* if any master is enabled, the slaves are too */
+       if (_desc.toggled) {
+               for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
                        if (mr->second.master()->get_value()) {
                                return _desc.upper;
                        }
-               } else {
-                       /* get current master value, scale by our current ratio with that master */
-                       v *= mr->second.master()->get_value () * mr->second.ratio();
                }
+               return _desc.lower;
+       }
+
+       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();
        }
 
-       return min (_desc.upper, v);
+       return min ((double) _desc.upper, v);
 }
 
 double
@@ -69,6 +68,16 @@ SlavableAutomationControl::get_value_locked() const
                return Control::get_double (false, _session.transport_frame());
        }
 
+       if (_desc.toggled) {
+               /* for boolean/toggle controls, if this slave OR any master is
+                * enabled, this slave is enabled. So check our own value
+                * first, because if we are enabled, we can return immediately.
+                */
+               if (Control::get_double (false, _session.transport_frame())) {
+                       return _desc.upper;
+               }
+       }
+
        return get_masters_value_locked ();
 }
 
@@ -86,20 +95,40 @@ SlavableAutomationControl::get_value() const
        }
 }
 
+void
+SlavableAutomationControl::actually_set_value (double val, Controllable::GroupControlDisposition group_override)
+{
+       val = std::max (std::min (val, (double)_desc.upper), (double)_desc.lower);
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (master_lock);
+
+               if (!_masters.empty()) {
+                       recompute_masters_ratios (val);
+               }
+       }
+
+       /* 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 ();
+}
+
 void
 SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
 {
-       double current_value;
-       double new_value;
        std::pair<Masters::iterator,bool> res;
 
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
-               current_value = get_value_locked ();
+               const double 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) {
 
@@ -110,21 +139,27 @@ 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));
+                       m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2, m));
                        cerr << this << enum_2_string ((AutomationType) _parameter.type()) << " now listening to Changed from " << m << endl;
                }
-
-               new_value = get_value_locked ();
        }
 
        if (res.second) {
@@ -132,26 +167,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)
 {
-       cerr << this << enum_2_string ((AutomationType)_parameter.type()) << " master changed, relay changed along\n";
-
-       /* our value has (likely) changed, but not because we were
-        * modified. Just the master.
-        */
+       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());
+               }
+       }
+}
 
-       Changed (false, gcd); /* EMIT SIGNAL */
+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
@@ -168,25 +245,43 @@ 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) {
+               if (erased && !_session.deletion_in_progress()) {
                        recompute_masters_ratios (current_value);
                }
+               masters_left = _masters.size ();
                new_value = get_value_locked ();
        }
 
+       if (_session.deletion_in_progress()) {
+               /* no reason to care about new values or sending signals */
+               return;
+       }
+
        if (erased) {
                MasterStatusChange (); /* EMIT SIGNAL */
        }
 
        if (new_value != current_value) {
-               Changed (false, Controllable::NoGroup);
+               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);
+               }
        }
+
+       /* no need to update boolean masters records, since the MR will have
+        * been removed already.
+        */
 }
 
 void
@@ -196,6 +291,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 ();
@@ -211,9 +309,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