1 /*
2  * Copyright (C) 2016-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2017-2018 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2017 Tim Mayberry <mojofunk@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #ifndef __libardour_slavable_automation_control_h__
22 #define __libardour_slavable_automation_control_h__
23 
24 #include "pbd/enumwriter.h"
25 #include "pbd/error.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/types_convert.h"
28 
29 #include "evoral/Curve.h"
30 
31 #include "ardour/audioengine.h"
32 #include "ardour/runtime_functions.h"
33 #include "ardour/slavable_automation_control.h"
34 #include "ardour/session.h"
35 
36 #include "pbd/i18n.h"
37 
38 using namespace std;
39 using namespace ARDOUR;
40 using namespace PBD;
41 
SlavableAutomationControl(ARDOUR::Session & s,const Evoral::Parameter & parameter,const ParameterDescriptor & desc,boost::shared_ptr<ARDOUR::AutomationList> l,const std::string & name,Controllable::Flag flags)42 SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
43                                                      const Evoral::Parameter&                  parameter,
44                                                      const ParameterDescriptor&                desc,
45                                                      boost::shared_ptr<ARDOUR::AutomationList> l,
46                                                      const std::string&                        name,
47                                                      Controllable::Flag                        flags)
48 	: AutomationControl (s, parameter, desc, l, name, flags)
49 	, _masters_node (0)
50 {
51 }
52 
~SlavableAutomationControl()53 SlavableAutomationControl::~SlavableAutomationControl ()
54 {
55 	if (_masters_node) {
56 		delete _masters_node;
57 		_masters_node = 0;
58 	}
59 }
60 
61 double
get_masters_value_locked() const62 SlavableAutomationControl::get_masters_value_locked () const
63 {
64 	if (_desc.toggled) {
65 		for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
66 			if (mr->second.master()->get_value()) {
67 				return _desc.upper;
68 			}
69 		}
70 		return _desc.lower;
71 	} else {
72 
73 		double v = 1.0; /* the masters function as a scaling factor */
74 
75 		for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
76 			v *= mr->second.master_ratio ();
77 		}
78 
79 		return v;
80 	}
81 }
82 
83 double
get_value_locked() const84 SlavableAutomationControl::get_value_locked() const
85 {
86 	/* read or write masters lock must be held */
87 
88 	if (_masters.empty()) {
89 		return Control::get_double (false, _session.transport_sample());
90 	}
91 
92 	if (_desc.toggled) {
93 		/* for boolean/toggle controls, if this slave OR any master is
94 		 * enabled, this slave is enabled. So check our own value
95 		 * first, because if we are enabled, we can return immediately.
96 		 */
97 		if (Control::get_double (false, _session.transport_sample())) {
98 			return _desc.upper;
99 		}
100 	}
101 
102 	return Control::get_double() * get_masters_value_locked ();
103 }
104 
105 /** Get the current effective `user' value based on automation state */
106 double
get_value() const107 SlavableAutomationControl::get_value() const
108 {
109 	bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
110 
111 	Glib::Threads::RWLock::ReaderLock lm (master_lock);
112 	if (!from_list) {
113 		if (!_masters.empty() && automation_write ()) {
114 			/* writing automation takes the fader value as-is, factor out the master */
115 			return Control::user_double ();
116 		}
117 		return get_value_locked ();
118 	} else {
119 		return Control::get_double (true, _session.transport_sample()) * get_masters_value_locked();
120 	}
121 }
122 
123 bool
get_masters_curve_locked(samplepos_t,samplepos_t,float *,samplecnt_t) const124 SlavableAutomationControl::get_masters_curve_locked (samplepos_t, samplepos_t, float*, samplecnt_t) const
125 {
126 	/* Every AutomationControl needs to implement this as-needed.
127 	 *
128 	 * This class also provides some convenient methods which
129 	 * could be used as defaults here (depending on  AutomationType)
130 	 * e.g. masters_curve_multiply()
131 	 */
132 	return false;
133 }
134 
135 bool
masters_curve_multiply(samplepos_t start,samplepos_t end,float * vec,samplecnt_t veclen) const136 SlavableAutomationControl::masters_curve_multiply (samplepos_t start, samplepos_t end, float* vec, samplecnt_t veclen) const
137 {
138 	gain_t* scratch = _session.scratch_automation_buffer ();
139 	bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
140 	bool rv = from_list && list()->curve().rt_safe_get_vector (start, end, scratch, veclen);
141 	if (rv) {
142 		for (samplecnt_t i = 0; i < veclen; ++i) {
143 			vec[i] *= scratch[i];
144 		}
145 	} else {
146 		apply_gain_to_buffer (vec, veclen, Control::get_double ());
147 	}
148 	if (_masters.empty()) {
149 		return rv;
150 	}
151 
152 	for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
153 		boost::shared_ptr<SlavableAutomationControl> sc
154 			= boost::dynamic_pointer_cast<SlavableAutomationControl>(mr->second.master());
155 		assert (sc);
156 		rv |= sc->masters_curve_multiply (start, end, vec, veclen);
157 		apply_gain_to_buffer (vec, veclen, mr->second.val_master_inv ());
158 	}
159 	return rv;
160 }
161 
162 double
reduce_by_masters_locked(double value,bool ignore_automation_state) const163 SlavableAutomationControl::reduce_by_masters_locked (double value, bool ignore_automation_state) const
164 {
165 	if (!_desc.toggled) {
166 		Glib::Threads::RWLock::ReaderLock lm (master_lock);
167 		if (!_masters.empty() && (ignore_automation_state || !automation_write ())) {
168 			/* need to scale given value by current master's scaling */
169 			const double masters_value = get_masters_value_locked();
170 			if (masters_value == 0.0) {
171 				value = 0.0;
172 			} else {
173 				value /= masters_value;
174 				value = std::max (lower(), std::min(upper(), value));
175 			}
176 		}
177 	}
178 	return value;
179 }
180 
181 void
actually_set_value(double value,PBD::Controllable::GroupControlDisposition gcd)182 SlavableAutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
183 {
184 	value = reduce_by_masters (value);
185 	/* this will call Control::set_double() and emit Changed signals as appropriate */
186 	AutomationControl::actually_set_value (value, gcd);
187 }
188 
189 void
add_master(boost::shared_ptr<AutomationControl> m)190 SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
191 {
192 	std::pair<Masters::iterator,bool> res;
193 
194 	{
195 		const double master_value = m->get_value();
196 		Glib::Threads::RWLock::WriterLock lm (master_lock);
197 
198 		pair<PBD::ID,MasterRecord> newpair (m->id(), MasterRecord (boost::weak_ptr<AutomationControl> (m), get_value_locked(), master_value));
199 		res = _masters.insert (newpair);
200 
201 		if (res.second) {
202 
203 			/* note that we bind @param m as a weak_ptr<AutomationControl>, thus
204 			   avoiding holding a reference to the control in the binding
205 			   itself.
206 			*/
207 			m->DropReferences.connect_same_thread (res.first->second.dropped_connection, boost::bind (&SlavableAutomationControl::master_going_away, this, boost::weak_ptr<AutomationControl>(m)));
208 
209 			/* Store the connection inside the MasterRecord, so
210 			   that when we destroy it, the connection is destroyed
211 			   and we no longer hear about changes to the
212 			   AutomationControl.
213 
214 			   Note that this also makes it safe to store a
215 			   boost::shared_ptr<AutomationControl> in the functor,
216 			   since we know we will destroy the functor when the
217 			   connection is destroyed, which happens when we
218 			   disconnect from the master (for any reason).
219 
220 			   Note that we fix the "from_self" argument that will
221 			   be given to our own Changed signal to "false",
222 			   because the change came from the master.
223 			*/
224 
225 			m->Changed.connect_same_thread (res.first->second.changed_connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2, boost::weak_ptr<AutomationControl>(m)));
226 		}
227 	}
228 
229 	if (res.second) {
230 		/* this will notify everyone that we're now slaved to the master */
231 		MasterStatusChange (); /* EMIT SIGNAL */
232 	}
233 
234 	post_add_master (m);
235 
236 	update_boolean_masters_records (m);
237 }
238 
239 int32_t
get_boolean_masters() const240 SlavableAutomationControl::get_boolean_masters () const
241 {
242 	int32_t n = 0;
243 
244 	if (_desc.toggled) {
245 		Glib::Threads::RWLock::ReaderLock lm (master_lock);
246 		for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
247 			if (mr->second.yn()) {
248 				++n;
249 			}
250 		}
251 	}
252 
253 	return n;
254 }
255 
256 void
update_boolean_masters_records(boost::shared_ptr<AutomationControl> m)257 SlavableAutomationControl::update_boolean_masters_records (boost::shared_ptr<AutomationControl> m)
258 {
259 	if (_desc.toggled) {
260 		/* We may modify a MasterRecord, but we not modify the master
261 		 * map, so we use a ReaderLock
262 		 */
263 		Glib::Threads::RWLock::ReaderLock lm (master_lock);
264 		Masters::iterator mi = _masters.find (m->id());
265 		if (mi != _masters.end()) {
266 			/* update MasterRecord to show whether the master is
267 			   on/off. We need to store this because the master
268 			   may change (in the sense of emitting Changed())
269 			   several times without actually changing the result
270 			   of ::get_value(). This is a feature of
271 			   AutomationControls (or even just Controllables,
272 			   really) which have more than a simple scalar
273 			   value. For example, the master may be a mute control
274 			   which can be muted_by_self() and/or
275 			   muted_by_masters(). When either of those two
276 			   conditions changes, Changed() will be emitted, even
277 			   though ::get_value() will return the same value each
278 			   time (1.0 if either are true, 0.0 if neither is).
279 
280 			   This provides a way for derived types to check
281 			   the last known state of a Master when the Master
282 			   changes. We update it after calling
283 			   ::master_changed() (though derived types must do
284 			   this themselves).
285 			*/
286 			mi->second.set_yn (m->get_value());
287 		}
288 	}
289 }
290 
291 void
master_changed(bool,GroupControlDisposition gcd,boost::weak_ptr<AutomationControl> wm)292 SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd, boost::weak_ptr<AutomationControl> wm)
293 {
294 	boost::shared_ptr<AutomationControl> m = wm.lock ();
295 	assert (m);
296 	Glib::Threads::RWLock::ReaderLock lm (master_lock);
297 	bool send_signal = handle_master_change (m);
298 	lm.release (); // update_boolean_masters_records() takes lock
299 
300 	update_boolean_masters_records (m);
301 	if (send_signal) {
302 		Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
303 	}
304 }
305 
306 void
master_going_away(boost::weak_ptr<AutomationControl> wm)307 SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl> wm)
308 {
309 	boost::shared_ptr<AutomationControl> m = wm.lock();
310 	if (m) {
311 		remove_master (m);
312 	}
313 }
314 
315 double
scale_automation_callback(double value,double ratio) const316 SlavableAutomationControl::scale_automation_callback (double value, double ratio) const
317 {
318 	/* derived classes can override this and e.g. add/subtract. */
319 	if (toggled ()) {
320 		// XXX we should use the master's upper/lower as threshold
321 		if (ratio >= 0.5 * (upper () - lower ())) {
322 			value = upper ();
323 		}
324 	} else {
325 		value *= ratio;
326 	}
327 	value = std::max (lower(), std::min(upper(), value));
328 	return value;
329 }
330 
331 void
remove_master(boost::shared_ptr<AutomationControl> m)332 SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
333 {
334 	if (_session.deletion_in_progress()) {
335 		/* no reason to care about new values or sending signals */
336 		return;
337 	}
338 
339 	pre_remove_master (m);
340 
341 	const double old_val = AutomationControl::get_double();
342 
343 	bool update_value = false;
344 	double master_ratio = 0;
345 	double list_ratio = toggled () ? 0 : 1;
346 
347 	boost::shared_ptr<AutomationControl> master;
348 
349 	{
350 		Glib::Threads::RWLock::WriterLock lm (master_lock);
351 
352 		Masters::const_iterator mi = _masters.find (m->id ());
353 
354 		if (mi != _masters.end()) {
355 			master_ratio = mi->second.master_ratio ();
356 			update_value = true;
357 			master = mi->second.master();
358 			list_ratio *= mi->second.val_master_inv ();
359 		}
360 
361 		if (!_masters.erase (m->id())) {
362 			return;
363 		}
364 	}
365 
366 	if (update_value) {
367 		/* when un-assigning we apply the master-value permanently */
368 		double new_val = old_val * master_ratio;
369 
370 		if (old_val != new_val) {
371 			AutomationControl::set_double (new_val, Controllable::NoGroup);
372 		}
373 
374 		/* ..and update automation */
375 		if (_list) {
376 			XMLNode* before = &alist ()->get_state ();
377 			if (master->automation_playback () && master->list()) {
378 				_list->list_merge (*master->list().get(), boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, _2));
379 				printf ("y-t %s  %f\n", name().c_str(), list_ratio);
380 				_list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, list_ratio));
381 			} else {
382 				// do we need to freeze/thaw the list? probably no: iterators & positions don't change
383 				_list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, master_ratio));
384 			}
385 			XMLNode* after = &alist ()->get_state ();
386 			if (*before != *after) {
387 				_session.begin_reversible_command (string_compose (_("Merge VCA automation into %1"), name ()));
388 				_session.commit_reversible_command (alist()->memento_command (before, after));
389 			}
390 		}
391 	}
392 
393 	MasterStatusChange (); /* EMIT SIGNAL */
394 
395 	/* no need to update boolean masters records, since the MR will have
396 	 * been removed already.
397 	 */
398 }
399 
400 void
clear_masters()401 SlavableAutomationControl::clear_masters ()
402 {
403 	if (_session.deletion_in_progress()) {
404 		/* no reason to care about new values or sending signals */
405 		return;
406 	}
407 
408 	const double old_val = AutomationControl::get_double();
409 
410 	ControlList masters;
411 	bool update_value = false;
412 	double master_ratio = 0;
413 	double list_ratio = toggled () ? 0 : 1;
414 
415 	/* null ptr means "all masters */
416 	pre_remove_master (boost::shared_ptr<AutomationControl>());
417 
418 	{
419 		Glib::Threads::RWLock::WriterLock lm (master_lock);
420 		if (_masters.empty()) {
421 			return;
422 		}
423 
424 		for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
425 			boost::shared_ptr<AutomationControl> master = mr->second.master();
426 			if (master->automation_playback () && master->list()) {
427 				masters.push_back (mr->second.master());
428 				list_ratio *= mr->second.val_master_inv ();
429 			} else {
430 				list_ratio *= mr->second.master_ratio ();
431 			}
432 		}
433 
434 		master_ratio = get_masters_value_locked ();
435 		update_value = true;
436 		_masters.clear ();
437 	}
438 
439 	if (update_value) {
440 		/* permanently apply masters value */
441 			double new_val = old_val * master_ratio;
442 
443 			if (old_val != new_val) {
444 				AutomationControl::set_double (new_val, Controllable::NoGroup);
445 			}
446 
447 			/* ..and update automation */
448 			if (_list) {
449 				XMLNode* before = &alist ()->get_state ();
450 				if (!masters.empty()) {
451 					for (ControlList::const_iterator m = masters.begin(); m != masters.end(); ++m) {
452 						_list->list_merge (*(*m)->list().get(), boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, _2));
453 					}
454 					_list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, list_ratio));
455 				} else {
456 					_list->y_transform (boost::bind (&SlavableAutomationControl::scale_automation_callback, this, _1, master_ratio));
457 				}
458 				XMLNode* after = &alist ()->get_state ();
459 				if (*before != *after) {
460 					_session.begin_reversible_command (string_compose (_("Merge VCA automation into %1"), name ()));
461 					_session.commit_reversible_command (alist()->memento_command (before, after));
462 				}
463 			}
464 	}
465 
466 	MasterStatusChange (); /* EMIT SIGNAL */
467 
468 	/* no need to update boolean masters records, since all MRs will have
469 	 * been removed already.
470 	 */
471 }
472 
473 bool
find_next_event_locked(double now,double end,Evoral::ControlEvent & next_event) const474 SlavableAutomationControl::find_next_event_locked (double now, double end, Evoral::ControlEvent& next_event) const
475 {
476 	if (_masters.empty()) {
477 		return false;
478 	}
479 	bool rv = false;
480 	/* iterate over all masters check their automation lists
481 	 * for any event between "now" and "end" which is earlier than
482 	 * next_event.when. If found, set next_event.when and return true.
483 	 * (see also Automatable::find_next_event)
484 	 */
485 	for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
486 		boost::shared_ptr<AutomationControl> ac (mr->second.master());
487 
488 		boost::shared_ptr<SlavableAutomationControl> sc
489 			= boost::dynamic_pointer_cast<SlavableAutomationControl>(ac);
490 
491 		if (sc && sc->find_next_event_locked (now, end, next_event)) {
492 			rv = true;
493 		}
494 
495 		Evoral::ControlList::const_iterator i;
496 		boost::shared_ptr<const Evoral::ControlList> alist (ac->list());
497 		Evoral::ControlEvent cp (now, 0.0f);
498 		if (!alist) {
499 			continue;
500 		}
501 
502 		for (i = lower_bound (alist->begin(), alist->end(), &cp, Evoral::ControlList::time_comparator);
503 		     i != alist->end() && (*i)->when < end; ++i) {
504 			if ((*i)->when > now) {
505 				break;
506 			}
507 		}
508 
509 		if (i != alist->end() && (*i)->when < end) {
510 			if ((*i)->when < next_event.when) {
511 				next_event.when = (*i)->when;
512 				rv = true;
513 			}
514 		}
515 	}
516 
517 	return rv;
518 }
519 
520 bool
handle_master_change(boost::shared_ptr<AutomationControl>)521 SlavableAutomationControl::handle_master_change (boost::shared_ptr<AutomationControl>)
522 {
523 	/* Derived classes can implement this for special cases (e.g. mute).
524 	 * This method is called with a ReaderLock (master_lock) held.
525 	 *
526 	 * return true if the changed master value resulted
527 	 * in a change of the control itself. */
528 	return true; // emit Changed
529 }
530 
531 void
automation_run(samplepos_t start,pframes_t nframes)532 SlavableAutomationControl::automation_run (samplepos_t start, pframes_t nframes)
533 {
534 	if (!automation_playback ()) {
535 		return;
536 	}
537 
538 	assert (_list);
539 	bool valid = false;
540 	double val = _list->rt_safe_eval (start, valid);
541 	if (!valid) {
542 		return;
543 	}
544 	if (toggled ()) {
545 		const double thresh = .5 * (_desc.upper - _desc.lower);
546 		bool on = (val >= thresh) || (get_masters_value () >= thresh);
547 		set_value_unchecked (on ? _desc.upper : _desc.lower);
548 	} else {
549 		set_value_unchecked (val * get_masters_value ());
550 	}
551 }
552 
553 bool
boolean_automation_run_locked(samplepos_t start,pframes_t len)554 SlavableAutomationControl::boolean_automation_run_locked (samplepos_t start, pframes_t len)
555 {
556 	bool rv = false;
557 	if (!_desc.toggled) {
558 		return false;
559 	}
560 	for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
561 		boost::shared_ptr<AutomationControl> ac (mr->second.master());
562 		if (!ac->automation_playback ()) {
563 			continue;
564 		}
565 		if (!ac->toggled ()) {
566 			continue;
567 		}
568 		boost::shared_ptr<SlavableAutomationControl> sc = boost::dynamic_pointer_cast<MuteControl>(ac);
569 		if (sc) {
570 			rv |= sc->boolean_automation_run (start, len);
571 		}
572 		boost::shared_ptr<const Evoral::ControlList> alist (ac->list());
573 		bool valid = false;
574 		const bool yn = alist->rt_safe_eval (start, valid) >= 0.5;
575 		if (!valid) {
576 			continue;
577 		}
578 		/* ideally we'd call just master_changed() which calls update_boolean_masters_records()
579 		 * but that takes the master_lock, which is already locked */
580 		if (mr->second.yn() != yn) {
581 			rv |= handle_master_change (ac);
582 			mr->second.set_yn (yn);
583 		}
584 	}
585 	return rv;
586 }
587 
588 bool
boolean_automation_run(samplepos_t start,pframes_t len)589 SlavableAutomationControl::boolean_automation_run (samplepos_t start, pframes_t len)
590 {
591 	bool change = false;
592 	{
593 		 Glib::Threads::RWLock::ReaderLock lm (master_lock);
594 		 change = boolean_automation_run_locked (start, len);
595 	}
596 	if (change) {
597 		Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
598 	}
599 	return change;
600 }
601 
602 bool
slaved_to(boost::shared_ptr<AutomationControl> m) const603 SlavableAutomationControl::slaved_to (boost::shared_ptr<AutomationControl> m) const
604 {
605 	Glib::Threads::RWLock::ReaderLock lm (master_lock);
606 	return _masters.find (m->id()) != _masters.end();
607 }
608 
609 bool
slaved() const610 SlavableAutomationControl::slaved () const
611 {
612 	Glib::Threads::RWLock::ReaderLock lm (master_lock);
613 	return !_masters.empty();
614 }
615 
616 int
set_state(XMLNode const & n,int)617 SlavableAutomationControl::MasterRecord::set_state (XMLNode const& n, int)
618 {
619 	n.get_property (X_("yn"), _yn);
620 	n.get_property (X_("val-ctrl"), _val_ctrl);
621 	n.get_property (X_("val-master"), _val_master);
622 	return 0;
623 }
624 
625 void
use_saved_master_ratios()626 SlavableAutomationControl::use_saved_master_ratios ()
627 {
628 	if (!_masters_node) {
629 		return;
630 	}
631 
632 	Glib::Threads::RWLock::ReaderLock lm (master_lock);
633 
634 	XMLNodeList nlist = _masters_node->children();
635 	XMLNodeIterator niter;
636 
637 	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
638 		ID id_val;
639 		if (!(*niter)->get_property (X_("id"), id_val)) {
640 			continue;
641 		}
642 		Masters::iterator mi = _masters.find (id_val);
643 		if (mi == _masters.end()) {
644 			continue;
645 		}
646 		mi->second.set_state (**niter, Stateful::loading_state_version);
647 	}
648 
649 	delete _masters_node;
650 	_masters_node = 0;
651 
652 	return;
653 }
654 
655 
656 XMLNode&
get_state()657 SlavableAutomationControl::get_state ()
658 {
659 	XMLNode& node (AutomationControl::get_state());
660 
661 	/* store VCA master ratios */
662 
663 	{
664 		Glib::Threads::RWLock::ReaderLock lm (master_lock);
665 		if (!_masters.empty()) {
666 			XMLNode* masters_node = new XMLNode (X_("masters"));
667 			for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
668 				XMLNode* mnode = new XMLNode (X_("master"));
669 				mnode->set_property (X_("id"), mr->second.master()->id());
670 
671 				if (_desc.toggled) {
672 					mnode->set_property (X_("yn"), mr->second.yn());
673 				} else {
674 					mnode->set_property (X_("val-ctrl"), mr->second.val_ctrl());
675 					mnode->set_property (X_("val-master"), mr->second.val_master());
676 				}
677 				masters_node->add_child_nocopy (*mnode);
678 			}
679 			node.add_child_nocopy (*masters_node);
680 		}
681 	}
682 
683 	return node;
684 }
685 
686 int
set_state(XMLNode const & node,int version)687 SlavableAutomationControl::set_state (XMLNode const& node, int version)
688 {
689 	XMLNodeList nlist = node.children();
690 	XMLNodeIterator niter;
691 
692 	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
693 		if ((*niter)->name() == X_("masters")) {
694 			_masters_node = new XMLNode (**niter);
695 		}
696 	}
697 
698 	return AutomationControl::set_state (node, version);
699 }
700 
701 
702 #endif /* __libardour_slavable_automation_control_h__ */
703