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