Consistent AU factory Preset IDs
[ardour.git] / libs / ardour / slavable_automation_control.cc
index 2f67946705fc1e73a7fce3d973a477b92ba84444..0bfa2b72496d2b6278e8fd889a124d19803d8641 100644 (file)
 #define __libardour_slavable_automation_control_h__
 
 #include "pbd/enumwriter.h"
+#include "pbd/error.h"
+#include "pbd/memento_command.h"
+#include "pbd/types_convert.h"
 
+#include "evoral/Curve.hpp"
+
+#include "ardour/audioengine.h"
+#include "ardour/runtime_functions.h"
 #include "ardour/slavable_automation_control.h"
 #include "ardour/session.h"
 
+#include "pbd/i18n.h"
+
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
@@ -35,14 +44,21 @@ SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
                                                      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) {
                        if (mr->second.master()->get_value()) {
@@ -50,14 +66,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 */
+
+               for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                       v *= mr->second.master_ratio ();
+               }
 
-       return min ((double) _desc.upper, v);
+               return v;
+       }
 }
 
 double
@@ -66,7 +84,7 @@ SlavableAutomationControl::get_value_locked() const
        /* read or write masters lock must be held */
 
        if (_masters.empty()) {
-               return Control::get_double (false, _session.transport_frame());
+               return Control::get_double (false, _session.transport_sample());
        }
 
        if (_desc.toggled) {
@@ -74,12 +92,12 @@ SlavableAutomationControl::get_value_locked() const
                 * 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())) {
+               if (Control::get_double (false, _session.transport_sample())) {
                        return _desc.upper;
                }
        }
 
-       return get_masters_value_locked ();
+       return Control::get_double() * get_masters_value_locked ();
 }
 
 /** Get the current effective `user' value based on automation state */
@@ -88,36 +106,82 @@ 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);
+               if (!_masters.empty() && automation_write ()) {
+                       /* writing automation takes the fader value as-is, factor out the master */
+                       return Control::user_double ();
+               }
                return get_value_locked ();
        } else {
-               return Control::get_double (from_list, _session.transport_frame());
+               return Control::get_double (true, _session.transport_sample()) * get_masters_value_locked();
        }
 }
 
 bool
-SlavableAutomationControl::actually_set_value (double val, Controllable::GroupControlDisposition group_override)
+SlavableAutomationControl::get_masters_curve_locked (samplepos_t, samplepos_t, float*, samplecnt_t) const
 {
-       val = std::max (std::min (val, (double)_desc.upper), (double)_desc.lower);
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (master_lock);
+       /* Every AutomationControl needs to implement this as-needed.
+        *
+        * This class also provides some convenient methods which
+        * could be used as defaults here (depending on  AutomationType)
+        * e.g. masters_curve_multiply()
+        */
+       return false;
+}
 
-               if (!_masters.empty()) {
-                       recompute_masters_ratios (val);
+bool
+SlavableAutomationControl::masters_curve_multiply (samplepos_t start, samplepos_t end, float* vec, samplecnt_t veclen) const
+{
+       gain_t* scratch = _session.scratch_automation_buffer ();
+       bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
+       bool rv = from_list && list()->curve().rt_safe_get_vector (start, end, scratch, veclen);
+       if (rv) {
+               for (samplecnt_t i = 0; i < veclen; ++i) {
+                       vec[i] *= scratch[i];
                }
+       } else {
+               apply_gain_to_buffer (vec, veclen, Control::get_double ());
+       }
+       if (_masters.empty()) {
+               return rv;
        }
 
-       /* this sets the Evoral::Control::_user_value for us, which will
-          be retrieved by AutomationControl::get_value ()
-       */
-       if (AutomationControl::actually_set_value (val, group_override)) {
-               _session.set_dirty ();
-               return true;
+       for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+               boost::shared_ptr<SlavableAutomationControl> sc
+                       = boost::dynamic_pointer_cast<SlavableAutomationControl>(mr->second.master());
+               assert (sc);
+               rv |= sc->masters_curve_multiply (start, end, vec, veclen);
+               apply_gain_to_buffer (vec, veclen, mr->second.val_master_inv ());
        }
+       return rv;
+}
 
-       return false;
+double
+SlavableAutomationControl::reduce_by_masters_locked (double value, bool ignore_automation_state) const
+{
+       if (!_desc.toggled) {
+               Glib::Threads::RWLock::ReaderLock lm (master_lock);
+               if (!_masters.empty() && (ignore_automation_state || !automation_write ())) {
+                       /* 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));
+                       }
+               }
+       }
+       return value;
+}
+
+void
+SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
+{
+       value = reduce_by_masters (value);
+       /* this will call Control::set_double() and emit Changed signals as appropriate */
+       AutomationControl::actually_set_value (value, gcd);
 }
 
 void
@@ -126,33 +190,28 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
        std::pair<Masters::iterator,bool> res;
 
        {
+               const double master_value = m->get_value();
                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));
+               pair<PBD::ID,MasterRecord> newpair (m->id(), MasterRecord (boost::weak_ptr<AutomationControl> (m), get_value_locked(), master_value));
                res = _masters.insert (newpair);
 
                if (res.second) {
 
-                       recompute_masters_ratios (current_value);
-
                        /* note that we bind @param m as a weak_ptr<AutomationControl>, thus
                           avoiding holding a reference to the control in the binding
                           itself.
                        */
-
-                       m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr<AutomationControl>(m)));
+                       m->DropReferences.connect_same_thread (res.first->second.dropped_connection, 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. 
+                          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 
+                          since we know we will destroy the functor when the
                           connection is destroyed, which happens when we
                           disconnect from the master (for any reason).
 
@@ -161,8 +220,7 @@ SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
                           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));
-                       cerr << this << enum_2_string ((AutomationType) _parameter.type()) << " now listening to Changed from " << m << endl;
+                       m->Changed.connect_same_thread (res.first->second.changed_connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2, boost::weak_ptr<AutomationControl>(m)));
                }
        }
 
@@ -229,10 +287,18 @@ SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr<Aut
 }
 
 void
-SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::shared_ptr<AutomationControl> m)
+SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::weak_ptr<AutomationControl> wm)
 {
+       boost::shared_ptr<AutomationControl> m = wm.lock ();
+       assert (m);
+       Glib::Threads::RWLock::ReaderLock lm (master_lock);
+       bool send_signal = handle_master_change (m);
+       lm.release (); // update_boolean_masters_records() takes lock
+
        update_boolean_masters_records (m);
-       Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
+       if (send_signal) {
+               Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
+       }
 }
 
 void
@@ -244,45 +310,86 @@ SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl>
        }
 }
 
+double
+SlavableAutomationControl::scale_automation_callback (double value, double ratio) const
+{
+       /* derived classes can override this and e.g. add/subtract. */
+       if (toggled ()) {
+               // XXX we should use the master's upper/lower as threshold
+               if (ratio >= 0.5 * (upper () - lower ())) {
+                       value = upper ();
+               }
+       } else {
+               value *= ratio;
+       }
+       value = std::max (lower(), std::min(upper(), value));
+       return value;
+}
+
 void
 SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
 {
-       double current_value;
-       double new_value;
-       bool masters_left;
-       Masters::size_type erased = 0;
+       if (_session.deletion_in_progress()) {
+               /* no reason to care about new values or sending signals */
+               return;
+       }
 
        pre_remove_master (m);
 
+       const double old_val = AutomationControl::get_double();
+
+       bool update_value = false;
+       double master_ratio = 0;
+       double list_ratio = toggled () ? 0 : 1;
+
+       boost::shared_ptr<AutomationControl> master;
+
        {
                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);
+
+               Masters::const_iterator mi = _masters.find (m->id ());
+
+               if (mi != _masters.end()) {
+                       master_ratio = mi->second.master_ratio ();
+                       update_value = true;
+                       master = mi->second.master();
+                       list_ratio *= mi->second.val_master_inv ();
                }
-               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 (!_masters.erase (m->id())) {
+                       return;
+               }
        }
 
-       if (erased) {
-               MasterStatusChange (); /* EMIT SIGNAL */
-       }
+       if (update_value) {
+               /* when un-assigning we apply the master-value permanently */
+               double new_val = old_val * master_ratio;
 
-       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 (old_val != new_val) {
+                       AutomationControl::set_double (new_val, Controllable::NoGroup);
+               }
+
+               /* ..and update automation */
+               if (_list) {
+                       XMLNode* before = &alist ()->get_state ();
+                       if (master->automation_playback () && master->list()) {
+                               _list->list_merge (*master->list().get(), boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, _2));
+                               printf ("y-t %s  %f\n", name().c_str(), list_ratio);
+                               _list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, list_ratio));
+                       } else {
+                               // do we need to freeze/thaw the list? probably no: iterators & positions don't change
+                               _list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, master_ratio));
+                       }
+                       XMLNode* after = &alist ()->get_state ();
+                       if (*before != *after) {
+                               _session.begin_reversible_command (string_compose (_("Merge VCA automation into %1"), name ()));
+                               _session.commit_reversible_command (alist()->memento_command (before, after));
+                       }
                }
        }
 
+       MasterStatusChange (); /* EMIT SIGNAL */
+
        /* no need to update boolean masters records, since the MR will have
         * been removed already.
         */
@@ -291,36 +398,205 @@ SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m
 void
 SlavableAutomationControl::clear_masters ()
 {
-       double current_value;
-       double new_value;
-       bool had_masters = false;
+       if (_session.deletion_in_progress()) {
+               /* no reason to care about new values or sending signals */
+               return;
+       }
+
+       const double old_val = AutomationControl::get_double();
+
+       ControlList masters;
+       bool update_value = false;
+       double master_ratio = 0;
+       double list_ratio = toggled () ? 0 : 1;
 
        /* null ptr means "all masters */
        pre_remove_master (boost::shared_ptr<AutomationControl>());
 
        {
                Glib::Threads::RWLock::WriterLock lm (master_lock);
-               current_value = get_value_locked ();
-               if (!_masters.empty()) {
-                       had_masters = true;
+               if (_masters.empty()) {
+                       return;
+               }
+
+               for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                       boost::shared_ptr<AutomationControl> master = mr->second.master();
+                       if (master->automation_playback () && master->list()) {
+                               masters.push_back (mr->second.master());
+                               list_ratio *= mr->second.val_master_inv ();
+                       } else {
+                               list_ratio *= mr->second.master_ratio ();
+                       }
                }
+
+               master_ratio = get_masters_value_locked ();
+               update_value = true;
                _masters.clear ();
-               new_value = get_value_locked ();
        }
 
-       if (had_masters) {
-               MasterStatusChange (); /* EMIT SIGNAL */
-       }
+       if (update_value) {
+               /* permanently apply masters value */
+                       double new_val = old_val * master_ratio;
+
+                       if (old_val != new_val) {
+                               AutomationControl::set_double (new_val, Controllable::NoGroup);
+                       }
 
-       if (new_value != current_value) {
-               actually_set_value (current_value, Controllable::UseGroup);
+                       /* ..and update automation */
+                       if (_list) {
+                               XMLNode* before = &alist ()->get_state ();
+                               if (!masters.empty()) {
+                                       for (ControlList::const_iterator m = masters.begin(); m != masters.end(); ++m) {
+                                               _list->list_merge (*(*m)->list().get(), boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, _2));
+                                       }
+                                       _list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, list_ratio));
+                               } else {
+                                       _list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, master_ratio));
+                               }
+                               XMLNode* after = &alist ()->get_state ();
+                               if (*before != *after) {
+                                       _session.begin_reversible_command (string_compose (_("Merge VCA automation into %1"), name ()));
+                                       _session.commit_reversible_command (alist()->memento_command (before, after));
+                               }
+                       }
        }
 
+       MasterStatusChange (); /* EMIT SIGNAL */
+
        /* no need to update boolean masters records, since all MRs will have
         * been removed already.
         */
 }
 
+bool
+SlavableAutomationControl::find_next_event_locked (double now, double end, Evoral::ControlEvent& next_event) const
+{
+       if (_masters.empty()) {
+               return false;
+       }
+       bool rv = false;
+       /* iterate over all masters check their automation lists
+        * for any event between "now" and "end" which is earlier than
+        * next_event.when. If found, set next_event.when and return true.
+        * (see also Automatable::find_next_event)
+        */
+       for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+               boost::shared_ptr<AutomationControl> ac (mr->second.master());
+
+               boost::shared_ptr<SlavableAutomationControl> sc
+                       = boost::dynamic_pointer_cast<SlavableAutomationControl>(ac);
+
+               if (sc && sc->find_next_event_locked (now, end, next_event)) {
+                       rv = true;
+               }
+
+               Evoral::ControlList::const_iterator i;
+               boost::shared_ptr<const Evoral::ControlList> alist (ac->list());
+               Evoral::ControlEvent cp (now, 0.0f);
+               if (!alist) {
+                       continue;
+               }
+
+               for (i = lower_bound (alist->begin(), alist->end(), &cp, Evoral::ControlList::time_comparator);
+                    i != alist->end() && (*i)->when < end; ++i) {
+                       if ((*i)->when > now) {
+                               break;
+                       }
+               }
+
+               if (i != alist->end() && (*i)->when < end) {
+                       if ((*i)->when < next_event.when) {
+                               next_event.when = (*i)->when;
+                               rv = true;
+                       }
+               }
+       }
+
+       return rv;
+}
+
+bool
+SlavableAutomationControl::handle_master_change (boost::shared_ptr<AutomationControl>)
+{
+       /* Derived classes can implement this for special cases (e.g. mute).
+        * This method is called with a ReaderLock (master_lock) held.
+        *
+        * return true if the changed master value resulted
+        * in a change of the control itself. */
+       return true; // emit Changed
+}
+
+void
+SlavableAutomationControl::automation_run (samplepos_t start, pframes_t nframes)
+{
+       if (!automation_playback ()) {
+               return;
+       }
+
+       assert (_list);
+       bool valid = false;
+       double val = _list->rt_safe_eval (start, valid);
+       if (!valid) {
+               return;
+       }
+       if (toggled ()) {
+               const double thresh = .5 * (_desc.upper - _desc.lower);
+               bool on = (val >= thresh) || (get_masters_value () >= thresh);
+               set_value_unchecked (on ? _desc.upper : _desc.lower);
+       } else {
+               set_value_unchecked (val * get_masters_value ());
+       }
+}
+
+bool
+SlavableAutomationControl::boolean_automation_run_locked (samplepos_t start, pframes_t len)
+{
+       bool rv = false;
+       if (!_desc.toggled) {
+               return false;
+       }
+       for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+               boost::shared_ptr<AutomationControl> ac (mr->second.master());
+               if (!ac->automation_playback ()) {
+                       continue;
+               }
+               if (!ac->toggled ()) {
+                       continue;
+               }
+               boost::shared_ptr<SlavableAutomationControl> sc = boost::dynamic_pointer_cast<MuteControl>(ac);
+               if (sc) {
+                       rv |= sc->boolean_automation_run (start, len);
+               }
+               boost::shared_ptr<const Evoral::ControlList> alist (ac->list());
+               bool valid = false;
+               const bool yn = alist->rt_safe_eval (start, valid) >= 0.5;
+               if (!valid) {
+                       continue;
+               }
+               /* ideally we'd call just master_changed() which calls update_boolean_masters_records()
+                * but that takes the master_lock, which is already locked */
+               if (mr->second.yn() != yn) {
+                       rv |= handle_master_change (ac);
+                       mr->second.set_yn (yn);
+               }
+       }
+       return rv;
+}
+
+bool
+SlavableAutomationControl::boolean_automation_run (samplepos_t start, pframes_t len)
+{
+       bool change = false;
+       {
+                Glib::Threads::RWLock::ReaderLock lm (master_lock);
+                change = boolean_automation_run_locked (start, len);
+       }
+       if (change) {
+               Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
+       }
+       return change;
+}
+
 bool
 SlavableAutomationControl::slaved_to (boost::shared_ptr<AutomationControl> m) const
 {
@@ -335,4 +611,90 @@ SlavableAutomationControl::slaved () const
        return !_masters.empty();
 }
 
+int
+SlavableAutomationControl::MasterRecord::set_state (XMLNode const& n, int)
+{
+       n.get_property (X_("yn"), _yn);
+       n.get_property (X_("val-ctrl"), _val_ctrl);
+       n.get_property (X_("val-master"), _val_master);
+       return 0;
+}
+
+void
+SlavableAutomationControl::use_saved_master_ratios ()
+{
+       if (!_masters_node) {
+               return;
+       }
+
+       Glib::Threads::RWLock::ReaderLock lm (master_lock);
+
+       XMLNodeList nlist = _masters_node->children();
+       XMLNodeIterator niter;
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               ID id_val;
+               if (!(*niter)->get_property (X_("id"), id_val)) {
+                       continue;
+               }
+               Masters::iterator mi = _masters.find (id_val);
+               if (mi == _masters.end()) {
+                       continue;
+               }
+               mi->second.set_state (**niter, Stateful::loading_state_version);
+       }
+
+       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"));
+                       for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+                               XMLNode* mnode = new XMLNode (X_("master"));
+                               mnode->set_property (X_("id"), mr->second.master()->id());
+
+                               if (_desc.toggled) {
+                                       mnode->set_property (X_("yn"), mr->second.yn());
+                               } else {
+                                       mnode->set_property (X_("val-ctrl"), mr->second.val_ctrl());
+                                       mnode->set_property (X_("val-master"), mr->second.val_master());
+                               }
+                               masters_node->add_child_nocopy (*mnode);
+                       }
+                       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__ */