X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fslavable_automation_control.cc;h=cb88d36fe9c55b023ce9c0e5f5a98b67359f05c6;hb=5694d877aaf4e69c90f76aa592e110289cbe81c6;hp=b9f7602dfc4e916925e873783687e4c7a4f5144e;hpb=8ee660356181f4eb66e4abcb30d52f55ae267b80;p=ardour.git diff --git a/libs/ardour/slavable_automation_control.cc b/libs/ardour/slavable_automation_control.cc index b9f7602dfc..cb88d36fe9 100644 --- a/libs/ardour/slavable_automation_control.cc +++ b/libs/ardour/slavable_automation_control.cc @@ -20,7 +20,14 @@ #define __libardour_slavable_automation_control_h__ #include "pbd/enumwriter.h" +#include "pbd/error.h" +#include "pbd/types_convert.h" +#include "pbd/i18n.h" +#include "evoral/Curve.hpp" + +#include "ardour/audioengine.h" +#include "ardour/runtime_functions.h" #include "ardour/slavable_automation_control.h" #include "ardour/session.h" @@ -32,16 +39,24 @@ SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s, const Evoral::Parameter& parameter, const ParameterDescriptor& desc, boost::shared_ptr 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) { if (mr->second.master()->get_value()) { @@ -49,14 +64,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_ratio (); + } + + return v; + } } double @@ -78,7 +95,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,69 +104,121 @@ SlavableAutomationControl::get_value() const { bool from_list = _list && boost::dynamic_pointer_cast(_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_frame()) * get_masters_value_locked(); } } -void -SlavableAutomationControl::actually_set_value (double val, Controllable::GroupControlDisposition group_override) +bool +SlavableAutomationControl::get_masters_curve_locked (framepos_t, framepos_t, float*, framecnt_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 (framepos_t start, framepos_t end, float* vec, framecnt_t veclen) const +{ + gain_t* scratch = _session.scratch_automation_buffer (); + bool from_list = _list && boost::dynamic_pointer_cast(_list)->automation_playback(); + bool rv = from_list && list()->curve().rt_safe_get_vector (start, end, scratch, veclen); + if (rv) { + for (framecnt_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; + } + + for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) { + boost::shared_ptr sc + = boost::dynamic_pointer_cast(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; +} - /* this sets the Evoral::Control::_user_value for us, which will - be retrieved by AutomationControl::get_value () - */ - AutomationControl::actually_set_value (val, group_override); +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; +} - _session.set_dirty (); +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 -SlavableAutomationControl::add_master (boost::shared_ptr m) +SlavableAutomationControl::add_master (boost::shared_ptr m, bool loading) { std::pair 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 */ - - res = _masters.insert (make_pair (m->id(), MasterRecord (m, 1.0))); + pair newpair (m->id(), MasterRecord (boost::weak_ptr (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, thus avoiding holding a reference to the control in the binding itself. */ + m->DropReferences.connect_same_thread (res.first->second.dropped_connection, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr(m))); - m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, 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 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)); - 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(m))); } } @@ -163,19 +232,21 @@ SlavableAutomationControl::add_master (boost::shared_ptr m) update_boolean_masters_records (m); } -bool +int32_t SlavableAutomationControl::get_boolean_masters () const { - if (!_desc.toggled) { - return false; - } + int32_t n = 0; - Glib::Threads::RWLock::ReaderLock lm (master_lock); - for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) { - if (mr->second.yn()) { - return true; + 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 @@ -197,7 +268,7 @@ SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr m) +SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::weak_ptr wm) { + boost::shared_ptr m = wm.lock (); + assert (m); + Glib::Threads::RWLock::ReaderLock lm (master_lock, Glib::Threads::TRY_LOCK); + if (!lm.locked ()) { + /* boolean_automation_run_locked () special case */ + return; + } + 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 @@ -229,40 +312,72 @@ SlavableAutomationControl::master_going_away (boost::weak_ptr } } +double +SlavableAutomationControl::scale_automation_callback (double value, double ratio) const +{ + /* derived classes can override this and e.g. add/subtract. */ + value *= ratio; + value = std::max (lower(), std::min(upper(), value)); + return value; +} + void SlavableAutomationControl::remove_master (boost::shared_ptr 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 = 1; + + boost::shared_ptr master; + { Glib::Threads::RWLock::WriterLock lm (master_lock); - current_value = get_value_locked (); - erased = _masters.erase (m->id()); - if (erased) { - 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 (erased) { - MasterStatusChange (); /* EMIT SIGNAL */ + if (!_masters.erase (m->id())) { + return; + } } - 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 (update_value) { + /* when un-assigning we apply the master-value permanently */ + double new_val = old_val * master_ratio; + + if (old_val != new_val) { + AutomationControl::set_double (new_val, Controllable::NoGroup); + } + + /* ..and update automation */ + if (_list) { + if (master->automation_playback () && master->list()) { + _list->list_merge (*master->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 { + // 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)); + } } } + MasterStatusChange (); /* EMIT SIGNAL */ + /* no need to update boolean masters records, since the MR will have * been removed already. */ @@ -271,36 +386,182 @@ SlavableAutomationControl::remove_master (boost::shared_ptr 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 = 1; /* null ptr means "all masters */ pre_remove_master (boost::shared_ptr()); { 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 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) { + 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)); + } + } } + 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 ac (mr->second.master()); + + boost::shared_ptr sc + = boost::dynamic_pointer_cast(ac); + + if (sc && sc->find_next_event_locked (now, end, next_event)) { + rv = true; + } + + Evoral::ControlList::const_iterator i; + boost::shared_ptr 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) +{ + /* 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 +} + +bool +SlavableAutomationControl::boolean_automation_run_locked (framepos_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 ac (mr->second.master()); + if (!ac->automation_playback ()) { + continue; + } + if (!ac->toggled ()) { + continue; + } + boost::shared_ptr sc = boost::dynamic_pointer_cast(ac); + if (sc) { + rv |= sc->boolean_automation_run (start, len); + } + boost::shared_ptr 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); + /* notify the GUI, without recursion: + * master_changed() above will ignore the change if the lock is held. + */ + ac->set_value_unchecked (yn ? 1. : 0.); + ac->Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */ + } + } + return rv; +} + +bool +SlavableAutomationControl::boolean_automation_run (framepos_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 m) const { @@ -315,4 +576,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__ */