X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fslavable_automation_control.cc;h=0bfa2b72496d2b6278e8fd889a124d19803d8641;hb=b285559767e21aae4467270590f048c3263fd742;hp=8a7a8ba00f79b9d28fe2c1fd8d9322ea817a1597;hpb=2bc2aea009d967fa23f9b04f0dbd2919e68aecb4;p=ardour.git diff --git a/libs/ardour/slavable_automation_control.cc b/libs/ardour/slavable_automation_control.cc index 8a7a8ba00f..0bfa2b7249 100644 --- a/libs/ardour/slavable_automation_control.cc +++ b/libs/ardour/slavable_automation_control.cc @@ -21,15 +21,18 @@ #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" +#include "pbd/i18n.h" + using namespace std; using namespace ARDOUR; using namespace PBD; @@ -56,7 +59,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()) { @@ -69,7 +71,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; @@ -82,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) { @@ -90,7 +92,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; } } @@ -106,14 +108,18 @@ 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(); } } bool -SlavableAutomationControl::get_masters_curve_locked (framepos_t, framepos_t, float*, framecnt_t) const +SlavableAutomationControl::get_masters_curve_locked (samplepos_t, samplepos_t, float*, samplecnt_t) const { /* Every AutomationControl needs to implement this as-needed. * @@ -125,54 +131,38 @@ SlavableAutomationControl::get_masters_curve_locked (framepos_t, framepos_t, flo } bool -SlavableAutomationControl::masters_curve_multiply (framepos_t start, framepos_t end, float* vec, framecnt_t veclen) const +SlavableAutomationControl::masters_curve_multiply (samplepos_t start, samplepos_t end, float* vec, samplecnt_t veclen) const { - bool rv = list()->curve().rt_safe_get_vector (start, end, vec, veclen); + 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; } - gain_t* scratch = _session.scratch_automation_buffer (); - for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) { - boost::shared_ptr ac (mr->second.master()); - bool got_curve; + for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) { boost::shared_ptr sc - = boost::dynamic_pointer_cast(ac); - if (sc) { - got_curve = sc->get_masters_curve_locked (start, end, scratch, veclen); - } else { - got_curve = ac->list()->curve().rt_safe_get_vector (start, end, scratch, veclen); - } - if (got_curve) { - // TODO use SSE/AVX methods, e.g. ARDOUR::apply_gain_to_buffer, mix_buffers_no_gain - // which works as long as automation _types_ gain_t == ARDOUR::Sample type == float - if (!rv) { - // TODO optimize this, in case rv is false, direcly use "vec" above. - rv = true; - memcpy (vec, scratch, sizeof (float) * veclen); - } else { - for (framecnt_t i = 0; i < veclen; ++i) { - vec[i] *= scratch[i]; - } - } - } else if (rv) { - const float v = get_masters_value (); - for (framecnt_t i = 0; i < veclen; ++i) { - vec[i] *= v; - } - } + = 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; } -void -SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd) +double +SlavableAutomationControl::reduce_by_masters_locked (double value, bool ignore_automation_state) const { if (!_desc.toggled) { - - Glib::Threads::RWLock::WriterLock lm (master_lock); - - if (!_masters.empty()) { + 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) { @@ -183,49 +173,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. */ - assert (masters_connections.find (boost::weak_ptr(m)) == masters_connections.end()); - PBD::ScopedConnection con; - m->DropReferences.connect_same_thread (con, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr(m))); - masters_connections[boost::weak_ptr(m)] = con; + 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 @@ -243,7 +220,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))); } } @@ -310,14 +287,14 @@ SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr m) +SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::weak_ptr wm) { - Glib::Threads::RWLock::ReaderLock lm (master_lock, Glib::Threads::TRY_LOCK); - if (!lm.locked ()) { - /* boolean_automation_run_locked () special case */ - return; - } + 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); if (send_signal) { Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */ @@ -333,43 +310,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_connections.erase (boost::weak_ptr(m)); + 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 @@ -380,32 +398,71 @@ 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 (); - masters_connections.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. */ @@ -461,11 +518,38 @@ SlavableAutomationControl::find_next_event_locked (double now, double end, Evora 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 (framepos_t start, pframes_t len) +SlavableAutomationControl::boolean_automation_run_locked (samplepos_t start, pframes_t len) { bool rv = false; if (!_desc.toggled) { @@ -494,18 +578,13 @@ SlavableAutomationControl::boolean_automation_run_locked (framepos_t start, pfra 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) +SlavableAutomationControl::boolean_automation_run (samplepos_t start, pframes_t len) { bool change = false; { @@ -532,6 +611,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 () { @@ -541,28 +629,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; @@ -581,22 +660,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); } }