X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fslavable_automation_control.cc;h=7d4d82252986f07f83e791e227cc4e38202e04c1;hb=9cf9833a93f5af0700dad44da4864565fa675862;hp=c06d8db7a5ca02143daf0dce5c8c32f2790ca322;hpb=e6eab345a16f791cac601fcf872d29a7337654db;p=ardour.git diff --git a/libs/ardour/slavable_automation_control.cc b/libs/ardour/slavable_automation_control.cc index c06d8db7a5..7d4d822529 100644 --- a/libs/ardour/slavable_automation_control.cc +++ b/libs/ardour/slavable_automation_control.cc @@ -21,10 +21,14 @@ #include "pbd/enumwriter.h" #include "pbd/error.h" +#include "pbd/memento_command.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" @@ -54,7 +58,6 @@ SlavableAutomationControl::~SlavableAutomationControl () double SlavableAutomationControl::get_masters_value_locked () const { - if (_desc.toggled) { for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) { if (mr->second.master()->get_value()) { @@ -67,7 +70,7 @@ SlavableAutomationControl::get_masters_value_locked () const 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()->get_value (); + v *= mr->second.master_ratio (); } return v; @@ -80,7 +83,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) { @@ -88,7 +91,7 @@ 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; } } @@ -104,20 +107,61 @@ SlavableAutomationControl::get_value() const Glib::Threads::RWLock::ReaderLock lm (master_lock); if (!from_list) { + 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 (true, _session.transport_frame()) * get_masters_value_locked(); + return Control::get_double (true, _session.transport_sample()) * get_masters_value_locked(); } } -void -SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd) +bool +SlavableAutomationControl::get_masters_curve_locked (samplepos_t, samplepos_t, float*, samplecnt_t) const { - if (!_desc.toggled) { + /* 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; +} - Glib::Threads::RWLock::WriterLock lm (master_lock); +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(_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; + } - if (!_masters.empty()) { + 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; +} + +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) { @@ -128,47 +172,36 @@ SlavableAutomationControl::actually_set_value (double value, PBD::Controllable:: } } } + 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 -SlavableAutomationControl::add_master (boost::shared_ptr m, bool loading) +SlavableAutomationControl::add_master (boost::shared_ptr m) { std::pair res; { + const double master_value = m->get_value(); Glib::Threads::RWLock::WriterLock lm (master_lock); - pair newpair (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) { - 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, 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(m))); + m->DropReferences.connect_same_thread (res.first->second.dropped_connection, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr(m))); /* Store the connection inside the MasterRecord, so that when we destroy it, the connection is destroyed @@ -186,7 +219,7 @@ SlavableAutomationControl::add_master (boost::shared_ptr m, b 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)); + m->Changed.connect_same_thread (res.first->second.changed_connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2, boost::weak_ptr(m))); } } @@ -253,10 +286,18 @@ 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); + 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 @@ -268,42 +309,84 @@ 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. */ + 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 m) { + 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 master; + { Glib::Threads::RWLock::WriterLock lm (master_lock); + 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 (); + } + if (!_masters.erase (m->id())) { return; } + } - if (!_session.deletion_in_progress()) { + if (update_value) { + /* when un-assigning we apply the master-value permanently */ + double new_val = old_val * master_ratio; - const double master_value = m->get_value (); + if (old_val != new_val) { + AutomationControl::set_double (new_val, Controllable::NoGroup); + } - 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. - */ + /* ..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 { - /* 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); + // 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)); } } } - 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 @@ -314,36 +397,205 @@ 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 = toggled () ? 0 : 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 (new_value != current_value) { - 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 (!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 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 +} + +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 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); + } + } + 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 m) const { @@ -358,6 +610,15 @@ 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 () { @@ -367,28 +628,19 @@ SlavableAutomationControl::use_saved_master_ratios () 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) { - ID id_val; - bool yn; - if (!(*niter)->get_property (X_("id"), id_val) || !(*niter)->get_property (X_("yn"), yn)) { - continue; - } + XMLNodeList nlist = _masters_node->children(); + XMLNodeIterator niter; - Masters::iterator mi = _masters.find (id_val); - if (mi != _masters.end()) { - mi->second.set_yn (yn); - } + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + ID id_val; + if (!(*niter)->get_property (X_("id"), id_val)) { + continue; } - - } else { - + Masters::iterator mi = _masters.find (id_val); + if (mi == _masters.end()) { + continue; + } + mi->second.set_state (**niter, Stateful::loading_state_version); } delete _masters_node; @@ -407,22 +659,20 @@ SlavableAutomationControl::get_state () { 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) { - 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()); - masters_node->add_child_nocopy (*mnode); + } else { + mnode->set_property (X_("val-ctrl"), mr->second.val_ctrl()); + mnode->set_property (X_("val-master"), mr->second.val_master()); } - } else { - + masters_node->add_child_nocopy (*mnode); } - node.add_child_nocopy (*masters_node); } }