redesign VCA control over gain (and theoretically other scalar controls)
authorPaul Davis <paul@linuxaudiosystems.com>
Tue, 21 Feb 2017 14:04:20 +0000 (15:04 +0100)
committerPaul Davis <paul@linuxaudiosystems.com>
Tue, 21 Feb 2017 14:07:07 +0000 (15:07 +0100)
master(s) value now just scales the control's own value; a trivial
bit of math at assign/deassign ensures that values do not change
during add/remove master operations

libs/ardour/ardour/gain_control.h
libs/ardour/ardour/slavable_automation_control.h
libs/ardour/automation_control.cc
libs/ardour/gain_control.cc
libs/ardour/slavable_automation_control.cc

index 53f429b88ae44c03411778e35072388ed3cc0fca..431342dab36e738b9399eca90e8b9d40a5999f17 100644 (file)
@@ -49,9 +49,6 @@ class LIBARDOUR_API GainControl : public SlavableAutomationControl {
        double range_db;
 
        void inc_gain (gain_t);
-
-  private:
-       void recompute_masters_ratios (double val);
 };
 
 } /* namespace */
index 857a8956e12d5451c09e6f75f28b4ab5fbdc604f..f1a78891e4ec196570e3c7afdd125cb1cf83c6ae 100644 (file)
@@ -70,7 +70,7 @@ class LIBARDOUR_API SlavableAutomationControl : public AutomationControl
           public:
                MasterRecord (boost::shared_ptr<AutomationControl> gc, double r)
                        : _master (gc)
-                       , _ratio (r)
+                       , _yn (false)
                {}
 
                boost::shared_ptr<AutomationControl> master() const { return _master; }
@@ -83,23 +83,12 @@ class LIBARDOUR_API SlavableAutomationControl : public AutomationControl
                bool yn() const { return _yn; }
                void set_yn (bool yn) { _yn = yn; }
 
-               /* for non-boolean/non-toggled controls, we store a ratio that
-                * connects the value of the master with the value of this
-                * slave. See comments in the source for more details on how
-                * this is computed and used.
-                */
-
-               double ratio () const { return _ratio; }
-               void reset_ratio (double r) { _ratio = r; }
-
                PBD::ScopedConnection connection;
 
          private:
                boost::shared_ptr<AutomationControl> _master;
-               union {
-                       double _ratio;
-                       bool   _yn;
-               };
+               /* holds most recently seen master value for boolean/toggle controls */
+               bool   _yn;
        };
 
        mutable Glib::Threads::RWLock master_lock;
@@ -109,11 +98,10 @@ class LIBARDOUR_API SlavableAutomationControl : public AutomationControl
 
        void   master_going_away (boost::weak_ptr<AutomationControl>);
        double get_value_locked() const;
-       void   actually_set_value (double val, PBD::Controllable::GroupControlDisposition group_override);
+       void   actually_set_value (double value, PBD::Controllable::GroupControlDisposition);
        void   update_boolean_masters_records (boost::shared_ptr<AutomationControl>);
 
        virtual void   master_changed (bool from_self, GroupControlDisposition gcd, boost::shared_ptr<AutomationControl>);
-       virtual void   recompute_masters_ratios (double val) { /* do nothing by default */}
        virtual double get_masters_value_locked () const;
        virtual void   pre_remove_master (boost::shared_ptr<AutomationControl>) {}
        virtual void   post_add_master (boost::shared_ptr<AutomationControl>) {}
index c090bca36417e6152b2502940aa4929af79074f2..0a29e7e6620d3f6b2ae20da8a91ec18af75ec94e 100644 (file)
@@ -169,9 +169,9 @@ AutomationControl::actually_set_value (double value, PBD::Controllable::GroupCon
        Control::set_double (value, pos, to_list);
 
        if (old_value != value) {
-               // AutomationType at = (AutomationType) _parameter.type();
-               // std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
-               // << " (was " << old_value << ") @ " << this << std::endl;
+               //AutomationType at = (AutomationType) _parameter.type();
+               //std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
+               //<< " (was " << old_value << ") @ " << this << std::endl;
 
                Changed (true, gcd);
                _session.set_dirty ();
index 21e1ba5f85c77e100c39f0f45b9b11fb2e183637..e6154495a4a40cba3c5a88b7484d12547ebcbfb0 100644 (file)
@@ -100,50 +100,3 @@ GainControl::inc_gain (gain_t factor)
        }
 }
 
-void
-GainControl::recompute_masters_ratios (double val)
-{
-       /* Master WRITE lock must be held */
-
-       /* V' is the new gain value for this
-
-          Mv(n) is the return value of ::get_value() for the n-th master
-          Mr(n) is the return value of ::ratio() for the n-th master record
-
-          the slave should return V' on the next call to ::get_value().
-
-          but the value is determined by the masters, so we know:
-
-          V' = (Mv(1) * Mr(1)) * (Mv(2) * Mr(2)) * ... * (Mv(n) * Mr(n))
-
-          hence:
-
-          Mr(1) * Mr(2) * ... * (Mr(n) = V' / (Mv(1) * Mv(2) * ... * Mv(n))
-
-          if we make all ratios equal (i.e. each master contributes the same
-          fraction of its own gain level to make the final slave gain), then we
-          have:
-
-          pow (Mr(n), n) = V' / (Mv(1) * Mv(2) * ... * Mv(n))
-
-          which gives
-
-          Mr(n) = pow ((V' / (Mv(1) * Mv(2) * ... * Mv(n))), 1/n)
-
-          Mr(n) is the new ratio number for the slaves
-       */
-
-       const double nmasters = _masters.size();
-       double masters_total_gain_coefficient = 1.0;
-
-       for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
-               masters_total_gain_coefficient *= mr->second.master()->get_value();
-       }
-
-       const double new_universal_ratio = pow ((val / masters_total_gain_coefficient), (1.0/nmasters));
-
-       for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
-               mr->second.reset_ratio (new_universal_ratio);
-       }
-}
-
index b76f91b13bb967d5bd11848e914a2e812662b0e3..9acf7444ad8114f2e0a3bc6fb197848091be59fa 100644 (file)
@@ -23,6 +23,7 @@
 #include "pbd/error.h"
 #include "pbd/i18n.h"
 
+#include "ardour/audioengine.h"
 #include "ardour/slavable_automation_control.h"
 #include "ardour/session.h"
 
@@ -52,7 +53,6 @@ SlavableAutomationControl::~SlavableAutomationControl ()
 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) {
@@ -61,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
@@ -90,7 +92,23 @@ SlavableAutomationControl::get_value_locked() const
                }
        }
 
-       return get_masters_value_locked ();
+       return Control::get_double() * get_masters_value_locked ();
+}
+
+void
+SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (master_lock);
+
+               if (!_masters.empty()) {
+                       /* need to scale given value by current master's scaling */
+                       value /= get_masters_value_locked();
+               }
+       }
+
+       /* this will call Control::set_double() and emit Changed signals as appropriate */
+       AutomationControl::actually_set_value (value, gcd);
 }
 
 /** Get the current effective `user' value based on automation state */
@@ -103,29 +121,10 @@ SlavableAutomationControl::get_value() const
        if (!from_list) {
                return get_value_locked ();
        } else {
-               return get_masters_value_locked () * 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)
-{
-       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);
-}
-
 void
 SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m, bool loading)
 {
@@ -133,9 +132,6 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m, b
 
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
-               const double current_value = get_value_locked ();
-
-               /* ratio will be recomputed below */
 
                pair<PBD::ID,MasterRecord> newpair (m->id(), MasterRecord (m, 1.0));
                res = _masters.insert (newpair);
@@ -143,7 +139,20 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m, b
                if (res.second) {
 
                        if (!loading) {
-                               recompute_masters_ratios (current_value);
+
+                               if (!_desc.toggled) {
+                                       const double master_value = m->get_value();
+
+                                       if (master_value == 0.0) {
+                                               actually_set_value (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
@@ -254,22 +263,34 @@ 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 && !_session.deletion_in_progress()) {
-                       recompute_masters_ratios (current_value);
+
+               if (!_masters.erase (m->id())) {
+                       return;
+               }
+
+               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);
+                       }
                }
-               masters_left = _masters.size ();
-               new_value = get_value_locked ();
        }
 
        if (_session.deletion_in_progress()) {
@@ -281,15 +302,6 @@ SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m
                MasterStatusChange (); /* EMIT SIGNAL */
        }
 
-       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);
-               }
-       }
-
        /* no need to update boolean masters records, since the MR will have
         * been removed already.
         */
@@ -375,19 +387,6 @@ SlavableAutomationControl::use_saved_master_ratios ()
 
        } else {
 
-               XMLProperty const * prop = _masters_node->property (X_("ratio"));
-
-               if (prop) {
-
-                       gain_t ratio;
-                       sscanf (prop->value().c_str(), "%g", &ratio);
-
-                       for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
-                               mr->second.reset_ratio (ratio);
-                       }
-               } else {
-                       PBD::error << string_compose (_("programming error: %1"), X_("missing ratio information for control slave"))<< endmsg;
-               }
        }
 
        delete _masters_node;
@@ -419,9 +418,7 @@ SlavableAutomationControl::get_state ()
                                        masters_node->add_child_nocopy (*mnode);
                                }
                        } else {
-                               XMLNode* masters_node = new XMLNode (X_("masters"));
-                               /* ratio is the same for all masters, so just store one */
-                               masters_node->add_property (X_("ratio"), PBD::to_string (_masters.begin()->second.ratio(), std::dec));
+
                        }
 
                        node.add_child_nocopy (*masters_node);