#ifndef __libardour_slavable_automation_control_h__
#define __libardour_slavable_automation_control_h__
-#include "ardour/automation_control.h"
+#include "pbd/enumwriter.h"
+
+#include "ardour/slavable_automation_control.h"
#include "ardour/session.h"
using namespace std;
{
}
-SlavableAutomationControl::~SlavableAutomationControl ()
-{
-
-}
-
double
SlavableAutomationControl::get_masters_value_locked () const
{
- gain_t v = _desc.normal;
+ double v = _desc.normal;
- for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
- if (_desc.toggled) {
- /* if any master is enabled, the slaves are too */
+ if (_desc.toggled) {
+ for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
if (mr->second.master()->get_value()) {
return _desc.upper;
}
- } else {
- /* get current master value, scale by our current ratio with that master */
- v *= mr->second.master()->get_value () * mr->second.ratio();
}
+ return _desc.lower;
+ }
+
+ 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();
}
- return min (_desc.upper, v);
+ return min ((double) _desc.upper, v);
}
double
return Control::get_double (false, _session.transport_frame());
}
+ if (_desc.toggled) {
+ /* for boolean/toggle controls, if this slave OR any master is
+ * 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())) {
+ return _desc.upper;
+ }
+ }
+
return 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);
+
+ _session.set_dirty ();
+}
+
void
SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
{
- double current_value;
- double new_value;
std::pair<Masters::iterator,bool> res;
{
Glib::Threads::RWLock::WriterLock lm (master_lock);
- current_value = get_value_locked ();
+ const double current_value = get_value_locked ();
/* ratio will be recomputed below */
- res = _masters.insert (make_pair<PBD::ID,MasterRecord> (m->id(), MasterRecord (m, 1.0)));
+ pair<PBD::ID,MasterRecord> newpair (m->id(), MasterRecord (m, 1.0));
+ res = _masters.insert (newpair);
if (res.second) {
itself.
*/
- m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, m));
+ m->DropReferences.connect_same_thread (masters_connections, 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.
- /* 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<AutomationControl> 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->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;
}
-
- new_value = get_value_locked ();
}
if (res.second) {
MasterStatusChange (); /* EMIT SIGNAL */
}
- if (new_value != current_value) {
- /* need to do this without a writable() check in case
- * the master is removed while this control is doing
- * automation playback.
- */
- actually_set_value (new_value, Controllable::NoGroup);
+ post_add_master (m);
+
+ update_boolean_masters_records (m);
+}
+
+int32_t
+SlavableAutomationControl::get_boolean_masters () const
+{
+ int32_t n = 0;
+
+ 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
-SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd)
+SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr<AutomationControl> m)
{
- cerr << this << enum_2_string ((AutomationType)_parameter.type()) << " master changed, relay changed along\n";
-
- /* our value has (likely) changed, but not because we were
- * modified. Just the master.
- */
+ if (_desc.toggled) {
+ /* We may modify a MasterRecord, but we not modify the master
+ * map, so we use a ReaderLock
+ */
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ Masters::iterator mi = _masters.find (m->id());
+ if (mi != _masters.end()) {
+ /* update MasterRecord to show whether the master is
+ on/off. We need to store this because the master
+ may change (in the sense of emitting Changed())
+ several times without actually changing the result
+ of ::get_value(). This is a feature of
+ AutomationControls (or even just Controllables,
+ really) which have more than a simple scalar
+ value. For example, the master may be a mute control
+ which can be muted_by_self() and/or
+ muted_by_masters(). When either of those two
+ conditions changes, Changed() will be emitted, even
+ though ::get_value() will return the same value each
+ time (1.0 if either are true, 0.0 if neither is).
+
+ This provides a way for derived types to check
+ the last known state of a Master when the Master
+ changes. We update it after calling
+ ::master_changed() (though derived types must do
+ this themselves).
+ */
+ mi->second.set_yn (m->get_value());
+ }
+ }
+}
- Changed (false, gcd); /* EMIT SIGNAL */
+void
+SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::shared_ptr<AutomationControl> m)
+{
+ update_boolean_masters_records (m);
+ Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
}
void
{
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) {
+ if (erased && !_session.deletion_in_progress()) {
recompute_masters_ratios (current_value);
}
+ 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 (erased) {
MasterStatusChange (); /* EMIT SIGNAL */
}
if (new_value != current_value) {
- Changed (false, Controllable::NoGroup);
+ 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.
+ */
}
void
double new_value;
bool had_masters = false;
+ /* 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 (new_value != current_value) {
- Changed (false, Controllable::NoGroup);
+ actually_set_value (current_value, Controllable::UseGroup);
}
+ /* no need to update boolean masters records, since all MRs will have
+ * been removed already.
+ */
}
bool