1 /*
2  * Copyright (C) 2001-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2007-2015 David Robillard <d@drobilla.net>
4  * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
5  * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
6  * Copyright (C) 2015-2018 Robin Gareus <robin@gareus.org>
7  * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 #include <cstdio>
25 #include <errno.h>
26 
27 #include "pbd/gstdio_compat.h"
28 #include <glibmm/miscutils.h>
29 
30 #include "pbd/error.h"
31 
32 #include "ardour/amp.h"
33 #include "ardour/automatable.h"
34 #include "ardour/event_type_map.h"
35 #include "ardour/gain_control.h"
36 #include "ardour/monitor_control.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/pan_controllable.h"
39 #include "ardour/pannable.h"
40 #include "ardour/plugin.h"
41 #include "ardour/plugin_insert.h"
42 #include "ardour/record_enable_control.h"
43 #include "ardour/session.h"
44 #include "ardour/uri_map.h"
45 #include "ardour/value_as_string.h"
46 
47 #include "pbd/i18n.h"
48 
49 using namespace std;
50 using namespace ARDOUR;
51 using namespace PBD;
52 
53 /* used for templates (previously: !full_state) */
54 bool Automatable::skip_saving_automation = false;
55 
56 const string Automatable::xml_node_name = X_("Automation");
57 
Automatable(Session & session)58 Automatable::Automatable(Session& session)
59 	: _a_session(session)
60 	, _automated_controls (new ControlList)
61 {
62 }
63 
Automatable(const Automatable & other)64 Automatable::Automatable (const Automatable& other)
65 	: ControlSet (other)
66 	, Slavable ()
67 	, _a_session (other._a_session)
68 	, _automated_controls (new ControlList)
69 {
70 	Glib::Threads::Mutex::Lock lm (other._control_lock);
71 
72 	for (Controls::const_iterator i = other._controls.begin(); i != other._controls.end(); ++i) {
73 		boost::shared_ptr<Evoral::Control> ac (control_factory (i->first));
74 		add_control (ac);
75 	}
76 }
77 
~Automatable()78 Automatable::~Automatable ()
79 {
80 	{
81 		RCUWriter<ControlList> writer (_automated_controls);
82 		boost::shared_ptr<ControlList> cl = writer.get_copy ();
83 		cl->clear ();
84 	}
85 	_automated_controls.flush ();
86 
87 	Glib::Threads::Mutex::Lock lm (_control_lock);
88 	for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
89 		boost::dynamic_pointer_cast<AutomationControl>(li->second)->drop_references ();
90 	}
91 }
92 
93 int
old_set_automation_state(const XMLNode & node)94 Automatable::old_set_automation_state (const XMLNode& node)
95 {
96 	XMLProperty const * prop;
97 
98 	if ((prop = node.property ("path")) != 0) {
99 		load_automation (prop->value());
100 	} else {
101 		warning << _("Automation node has no path property") << endmsg;
102 	}
103 
104 	return 0;
105 }
106 
107 int
load_automation(const string & path)108 Automatable::load_automation (const string& path)
109 {
110 	string fullpath;
111 
112 	if (Glib::path_is_absolute (path)) { // legacy
113 		fullpath = path;
114 	} else {
115 		fullpath = _a_session.automation_dir();
116 		fullpath += path;
117 	}
118 
119 	FILE * in = g_fopen (fullpath.c_str (), "rb");
120 
121 	if (!in) {
122 		warning << string_compose(_("cannot open %2 to load automation data (%3)")
123 				, fullpath, strerror (errno)) << endmsg;
124 		return 1;
125 	}
126 
127 	Glib::Threads::Mutex::Lock lm (control_lock());
128 	set<Evoral::Parameter> tosave;
129 	controls().clear ();
130 
131 	while (!feof(in)) {
132 		double when;
133 		double value;
134 		uint32_t port;
135 
136 		if (3 != fscanf (in, "%d %lf %lf", &port, &when, &value)) {
137 			if (feof(in)) {
138 				break;
139 			}
140 			goto bad;
141 		}
142 
143 		Evoral::Parameter param(PluginAutomation, 0, port);
144 		/* FIXME: this is legacy and only used for plugin inserts?  I think? */
145 		boost::shared_ptr<Evoral::Control> c = control (param, true);
146 		c->list()->add (when, value);
147 		tosave.insert (param);
148 	}
149 	::fclose (in);
150 
151 	return 0;
152 
153 bad:
154 	error << string_compose(_("cannot load automation data from %2"), fullpath) << endmsg;
155 	controls().clear ();
156 	::fclose (in);
157 	return -1;
158 }
159 
160 void
add_control(boost::shared_ptr<Evoral::Control> ac)161 Automatable::add_control(boost::shared_ptr<Evoral::Control> ac)
162 {
163 	Evoral::Parameter param = ac->parameter();
164 
165 	boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (ac->list ());
166 
167 	boost::shared_ptr<AutomationControl> actl (boost::dynamic_pointer_cast<AutomationControl> (ac));
168 
169 	if ((!actl || !(actl->flags() & Controllable::NotAutomatable)) && al) {
170 		al->automation_state_changed.connect_same_thread (
171 			_list_connections,
172 			boost::bind (&Automatable::automation_list_automation_state_changed,
173 			             this, ac->parameter(), _1));
174 	}
175 
176 	ControlSet::add_control (ac);
177 
178 	if ((!actl || !(actl->flags() & Controllable::NotAutomatable)) && al) {
179 		if (!actl || !(actl->flags() & Controllable::HiddenControl)) {
180 			can_automate (param);
181 		}
182 		automation_list_automation_state_changed (param, al->automation_state ()); // sync everything up
183 	}
184 }
185 
186 string
describe_parameter(Evoral::Parameter param)187 Automatable::describe_parameter (Evoral::Parameter param)
188 {
189 	/* derived classes like PluginInsert should override this */
190 
191 	if (param == Evoral::Parameter(GainAutomation)) {
192 		return _("Fader");
193 	} else if (param.type() == BusSendLevel) {
194 		return _("Send");
195 	} else if (param.type() == TrimAutomation) {
196 		return _("Trim");
197 	} else if (param.type() == MainOutVolume) {
198 		return _("Master Volume");
199 	} else if (param.type() == MuteAutomation) {
200 		return _("Mute");
201 	} else if (param.type() == PanAzimuthAutomation) {
202 		return _("Azimuth");
203 	} else if (param.type() == PanWidthAutomation) {
204 		return _("Width");
205 	} else if (param.type() == PanElevationAutomation) {
206 		return _("Elevation");
207 	} else if (param.type() == MidiCCAutomation) {
208 		return string_compose("Controller %1 [%2]", param.id(), int(param.channel()) + 1);
209 	} else if (param.type() == MidiPgmChangeAutomation) {
210 		return string_compose("Program [%1]", int(param.channel()) + 1);
211 	} else if (param.type() == MidiPitchBenderAutomation) {
212 		return string_compose("Bender [%1]", int(param.channel()) + 1);
213 	} else if (param.type() == MidiChannelPressureAutomation) {
214 		return string_compose("Pressure [%1]", int(param.channel()) + 1);
215 	} else if (param.type() == MidiNotePressureAutomation) {
216 		return string_compose("PolyPressure [%1]", int(param.channel()) + 1);
217 	} else if (param.type() == PluginPropertyAutomation) {
218 		return string_compose("Property %1", URIMap::instance().id_to_uri(param.id()));
219 	} else {
220 		return EventTypeMap::instance().to_symbol(param);
221 	}
222 }
223 
224 void
can_automate(Evoral::Parameter what)225 Automatable::can_automate (Evoral::Parameter what)
226 {
227 	_can_automate_list.insert (what);
228 }
229 
230 std::vector<Evoral::Parameter>
all_automatable_params() const231 Automatable::all_automatable_params () const
232 {
233 	return std::vector<Evoral::Parameter> (_can_automate_list.begin (), _can_automate_list.end ());
234 }
235 
236 /** \a legacy_param is used for loading legacy sessions where an object (IO, Panner)
237  * had a single automation parameter, with it's type implicit.  Derived objects should
238  * pass that type and it will be used for the untyped AutomationList found.
239  */
240 int
set_automation_xml_state(const XMLNode & node,Evoral::Parameter legacy_param)241 Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter legacy_param)
242 {
243 	Glib::Threads::Mutex::Lock lm (control_lock());
244 
245 	/* Don't clear controls, since some may be special derived Controllable classes */
246 
247 	XMLNodeList nlist = node.children();
248 	XMLNodeIterator niter;
249 
250 	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
251 
252 		/*if (sscanf ((*niter)->name().c_str(), "parameter-%" PRIu32, &param) != 1) {
253 		  error << string_compose (_("%2: badly formatted node name in XML automation state, ignored"), _name) << endmsg;
254 		  continue;
255 		  }*/
256 
257 		if ((*niter)->name() == "AutomationList") {
258 
259 			XMLProperty const * id_prop = (*niter)->property("automation-id");
260 
261 			Evoral::Parameter param = (id_prop
262 					? EventTypeMap::instance().from_symbol(id_prop->value())
263 					: legacy_param);
264 
265 			if (param.type() == NullAutomation) {
266 				warning << "Automation has null type" << endl;
267 				continue;
268 			}
269 
270 			if (!id_prop) {
271 				warning << "AutomationList node without automation-id property, "
272 					<< "using default: " << EventTypeMap::instance().to_symbol(legacy_param) << endmsg;
273 			}
274 
275 			if (_can_automate_list.find (param) == _can_automate_list.end ()) {
276 				boost::shared_ptr<AutomationControl> actl = automation_control (param);
277 				if (actl && (*niter)->children().size() > 0 && Config->get_limit_n_automatables () > 0) {
278 					actl->clear_flag (Controllable::NotAutomatable);
279 					if (!(actl->flags() & Controllable::HiddenControl) && actl->name() != X_("hidden")) {
280 						can_automate (param);
281 					}
282 					info << "Marked parmater as automatable" << endl;
283 				} else {
284 					warning << "Ignored automation data for non-automatable parameter" << endl;
285 					continue;
286 				}
287 			}
288 
289 
290 			boost::shared_ptr<AutomationControl> existing = automation_control (param);
291 
292 			if (existing) {
293 				existing->alist()->set_state (**niter, Stateful::loading_state_version);
294 			} else {
295 				boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
296 				add_control (newcontrol);
297 				boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
298 				newcontrol->set_list(al);
299 			}
300 
301 		} else {
302 			error << "Expected AutomationList node, got '" << (*niter)->name() << "'" << endmsg;
303 		}
304 	}
305 
306 	return 0;
307 }
308 
309 XMLNode&
get_automation_xml_state()310 Automatable::get_automation_xml_state ()
311 {
312 	Glib::Threads::Mutex::Lock lm (control_lock());
313 	XMLNode* node = new XMLNode (Automatable::xml_node_name);
314 
315 	if (controls().empty()) {
316 		return *node;
317 	}
318 
319 	for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
320 		boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(li->second->list());
321 		if (l) {
322 			node->add_child_nocopy (l->get_state ());
323 		}
324 	}
325 
326 	return *node;
327 }
328 
329 void
set_parameter_automation_state(Evoral::Parameter param,AutoState s)330 Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState s)
331 {
332 	Glib::Threads::Mutex::Lock lm (control_lock());
333 
334 	boost::shared_ptr<AutomationControl> c = automation_control (param, true);
335 
336 	if (c && (s != c->automation_state())) {
337 		c->set_automation_state (s);
338 		_a_session.set_dirty ();
339 		AutomationStateChanged(); /* Emit signal */
340 	}
341 }
342 
343 AutoState
get_parameter_automation_state(Evoral::Parameter param)344 Automatable::get_parameter_automation_state (Evoral::Parameter param)
345 {
346 	AutoState result = Off;
347 
348 	boost::shared_ptr<AutomationControl> c = automation_control(param);
349 
350 	if (c) {
351 		result = c->automation_state();
352 	}
353 
354 	return result;
355 }
356 
357 void
protect_automation()358 Automatable::protect_automation ()
359 {
360 	typedef set<Evoral::Parameter> ParameterSet;
361 	const ParameterSet& automated_params = what_can_be_automated ();
362 
363 	for (ParameterSet::const_iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
364 
365 		boost::shared_ptr<Evoral::Control> c = control(*i);
366 		boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
367 
368 		switch (l->automation_state()) {
369 		case Write:
370 			l->set_automation_state (Off);
371 			break;
372 		case Latch:
373 			/* fallthrough */
374 		case Touch:
375 			l->set_automation_state (Play);
376 			break;
377 		default:
378 			break;
379 		}
380 	}
381 }
382 
383 void
non_realtime_locate(samplepos_t now)384 Automatable::non_realtime_locate (samplepos_t now)
385 {
386 	bool rolling = _a_session.transport_rolling ();
387 
388 	for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
389 
390 		boost::shared_ptr<AutomationControl> c
391 				= boost::dynamic_pointer_cast<AutomationControl>(li->second);
392 		if (c) {
393 			boost::shared_ptr<AutomationList> l
394 				= boost::dynamic_pointer_cast<AutomationList>(c->list());
395 
396 			if (!l) {
397 				continue;
398 			}
399 
400 			bool am_touching = c->touching ();
401 			if (rolling && am_touching) {
402 			/* when locating while rolling, and writing automation,
403 			 * start a new write pass.
404 			 * compare to compare to non_realtime_transport_stop()
405 			 */
406 				const bool list_did_write = !l->in_new_write_pass ();
407 				c->stop_touch (-1); // time is irrelevant
408 				l->stop_touch (-1);
409 				c->commit_transaction (list_did_write);
410 				l->write_pass_finished (now, Config->get_automation_thinning_factor ());
411 
412 				if (l->automation_state () == Write) {
413 					l->set_automation_state (Touch);
414 				}
415 				if (l->automation_playback ()) {
416 					c->set_value_unchecked (c->list ()->eval (now));
417 				}
418 			}
419 
420 			l->start_write_pass (now);
421 
422 			if (rolling && am_touching) {
423 				c->start_touch (now);
424 			}
425 		}
426 	}
427 }
428 
429 void
non_realtime_transport_stop(samplepos_t now,bool)430 Automatable::non_realtime_transport_stop (samplepos_t now, bool /*flush_processors*/)
431 {
432 	for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
433 		boost::shared_ptr<AutomationControl> c =
434 			boost::dynamic_pointer_cast<AutomationControl>(li->second);
435 		if (!c) {
436 			continue;
437 		}
438 
439 		boost::shared_ptr<AutomationList> l =
440 			boost::dynamic_pointer_cast<AutomationList>(c->list());
441 		if (!l) {
442 			continue;
443 		}
444 
445 		/* Stop any active touch gesture just before we mark the write pass
446 		   as finished.  If we don't do this, the transport can end up stopped with
447 		   an AutomationList thinking that a touch is still in progress and,
448 		   when the transport is re-started, a touch will magically
449 		   be happening without it ever have being started in the usual way.
450 		*/
451 		const bool list_did_write = !l->in_new_write_pass ();
452 
453 		c->stop_touch (now);
454 		l->stop_touch (now);
455 
456 		c->commit_transaction (list_did_write);
457 
458 		l->write_pass_finished (now, Config->get_automation_thinning_factor ());
459 
460 		if (l->automation_state () == Write) {
461 			l->set_automation_state (Touch);
462 		}
463 
464 		if (l->automation_playback ()) {
465 			c->set_value_unchecked (c->list ()->eval (now));
466 		}
467 	}
468 }
469 
470 void
automation_run(samplepos_t start,pframes_t nframes,bool only_active)471 Automatable::automation_run (samplepos_t start, pframes_t nframes, bool only_active)
472 {
473 	if (only_active) {
474 		boost::shared_ptr<ControlList> cl = _automated_controls.reader ();
475 		for (ControlList::const_iterator ci = cl->begin(); ci != cl->end(); ++ci) {
476 			(*ci)->automation_run (start, nframes);
477 		}
478 		return;
479 	}
480 
481 	for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
482 		boost::shared_ptr<AutomationControl> c =
483 			boost::dynamic_pointer_cast<AutomationControl>(li->second);
484 		if (!c) {
485 			continue;
486 		}
487 		c->automation_run (start, nframes);
488 	}
489 }
490 
491 void
automation_list_automation_state_changed(Evoral::Parameter param,AutoState as)492 Automatable::automation_list_automation_state_changed (Evoral::Parameter param, AutoState as)
493 {
494 	{
495 		boost::shared_ptr<AutomationControl> c (automation_control(param));
496 		assert (c && c->list());
497 
498 		RCUWriter<ControlList> writer (_automated_controls);
499 		boost::shared_ptr<ControlList> cl = writer.get_copy ();
500 
501 		ControlList::iterator fi = std::find (cl->begin(), cl->end(), c);
502 		if (fi != cl->end()) {
503 			cl->erase (fi);
504 		}
505 		switch (as) {
506 			/* all potential  automation_playback() states */
507 			case Play:
508 			case Touch:
509 			case Latch:
510 				cl->push_back (c);
511 				break;
512 			case Off:
513 			case Write:
514 				break;
515 		}
516 	}
517 	_automated_controls.flush();
518 }
519 
520 boost::shared_ptr<Evoral::Control>
control_factory(const Evoral::Parameter & param)521 Automatable::control_factory(const Evoral::Parameter& param)
522 {
523 	Evoral::Control*                  control   = NULL;
524 	bool                              make_list = true;
525 	ParameterDescriptor               desc(param);
526 	boost::shared_ptr<AutomationList> list;
527 
528 	if (parameter_is_midi (param.type())) {
529 		MidiTrack* mt = dynamic_cast<MidiTrack*>(this);
530 		if (mt) {
531 			control = new MidiTrack::MidiControl(mt, param);
532 			make_list = false;  // No list, this is region "automation"
533 		}
534 	} else if (param.type() == PluginAutomation) {
535 		PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
536 		if (pi) {
537 			pi->plugin(0)->get_parameter_descriptor(param.id(), desc);
538 			control = new PluginInsert::PluginControl(pi, param, desc);
539 		} else {
540 			warning << "PluginAutomation for non-Plugin" << endl;
541 		}
542 	} else if (param.type() == PluginPropertyAutomation) {
543 		PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
544 		if (pi) {
545 			desc = pi->plugin(0)->get_property_descriptor(param.id());
546 			if (desc.datatype != Variant::NOTHING) {
547 				if (!Variant::type_is_numeric(desc.datatype)) {
548 					make_list = false;  // Can't automate non-numeric data yet
549 				} else {
550 					list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
551 				}
552 				control = new PluginInsert::PluginPropertyControl(pi, param, desc, list);
553 			}
554 		} else {
555 			warning << "PluginPropertyAutomation for non-Plugin" << endl;
556 		}
557 	} else if (param.type() == GainAutomation) {
558 		control = new GainControl(_a_session, param);
559 	} else if (param.type() == TrimAutomation) {
560 		control = new GainControl(_a_session, param);
561 	} else if (param.type() == MainOutVolume) {
562 		control = new GainControl(_a_session, param);
563 	} else if (param.type() == BusSendLevel) {
564 		control = new GainControl(_a_session, param);
565 	} else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
566 		Pannable* pannable = dynamic_cast<Pannable*>(this);
567 		if (pannable) {
568 			control = new PanControllable (_a_session, describe_parameter (param), pannable, param);
569 		} else {
570 			warning << "PanAutomation for non-Pannable" << endl;
571 		}
572 	} else if (param.type() == RecEnableAutomation) {
573 		Recordable* re = dynamic_cast<Recordable*> (this);
574 		if (re) {
575 			control = new RecordEnableControl (_a_session, X_("recenable"), *re);
576 		}
577 	} else if (param.type() == MonitoringAutomation) {
578 		Monitorable* m = dynamic_cast<Monitorable*>(this);
579 		if (m) {
580 			control = new MonitorControl (_a_session, X_("monitor"), *m);
581 		}
582 	} else if (param.type() == SoloAutomation) {
583 		Soloable* s = dynamic_cast<Soloable*>(this);
584 		Muteable* m = dynamic_cast<Muteable*>(this);
585 		if (s && m) {
586 			control = new SoloControl (_a_session, X_("solo"), *s, *m);
587 		}
588 	} else if (param.type() == MuteAutomation) {
589 		Muteable* m = dynamic_cast<Muteable*>(this);
590 		if (m) {
591 			control = new MuteControl (_a_session, X_("mute"), *m);
592 		}
593 	}
594 
595 	if (make_list && !list) {
596 		list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
597 	}
598 
599 	if (!control) {
600 		control = new AutomationControl(_a_session, param, desc, list);
601 	}
602 
603 	return boost::shared_ptr<Evoral::Control>(control);
604 }
605 
606 boost::shared_ptr<AutomationControl>
automation_control(PBD::ID const & id) const607 Automatable::automation_control (PBD::ID const & id) const
608 {
609 	Controls::const_iterator li;
610 
611 	for (li = _controls.begin(); li != _controls.end(); ++li) {
612 		boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (li->second);
613 		if (ac && (ac->id() == id)) {
614 			return ac;
615 		}
616 	}
617 
618 	return boost::shared_ptr<AutomationControl>();
619 }
620 
621 boost::shared_ptr<AutomationControl>
automation_control(const Evoral::Parameter & id,bool create)622 Automatable::automation_control (const Evoral::Parameter& id, bool create)
623 {
624 	return boost::dynamic_pointer_cast<AutomationControl>(Evoral::ControlSet::control(id, create));
625 }
626 
627 boost::shared_ptr<const AutomationControl>
automation_control(const Evoral::Parameter & id) const628 Automatable::automation_control (const Evoral::Parameter& id) const
629 {
630 	return boost::dynamic_pointer_cast<const AutomationControl>(Evoral::ControlSet::control(id));
631 }
632 
633 void
clear_controls()634 Automatable::clear_controls ()
635 {
636 	_control_connections.drop_connections ();
637 	ControlSet::clear_controls ();
638 }
639 
640 bool
find_next_event(double start,double end,Evoral::ControlEvent & next_event,bool only_active) const641 Automatable::find_next_event (double start, double end, Evoral::ControlEvent& next_event, bool only_active) const
642 {
643 	next_event.when = start <= end ? std::numeric_limits<double>::max() : 0;
644 
645 	if (only_active) {
646 		boost::shared_ptr<ControlList> cl = _automated_controls.reader ();
647 		for (ControlList::const_iterator ci = cl->begin(); ci != cl->end(); ++ci) {
648 			if ((*ci)->automation_playback()) {
649 				if (start <= end) {
650 					find_next_ac_event (*ci, start, end, next_event);
651 				} else {
652 					find_prev_ac_event (*ci, start, end, next_event);
653 				}
654 			}
655 		}
656 	} else {
657 		for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
658 			boost::shared_ptr<AutomationControl> c
659 				= boost::dynamic_pointer_cast<AutomationControl>(li->second);
660 			if (c) {
661 				if (start <= end) {
662 					find_next_ac_event (c, start, end, next_event);
663 				} else {
664 					find_prev_ac_event (c, start, end, next_event);
665 				}
666 			}
667 		}
668 	}
669 	return next_event.when != (start <= end ? std::numeric_limits<double>::max() : 0);
670 }
671 
672 void
find_next_ac_event(boost::shared_ptr<AutomationControl> c,double start,double end,Evoral::ControlEvent & next_event) const673 Automatable::find_next_ac_event (boost::shared_ptr<AutomationControl> c, double start, double end, Evoral::ControlEvent& next_event) const
674 {
675 	assert (start <= end);
676 
677 	boost::shared_ptr<SlavableAutomationControl> sc
678 		= boost::dynamic_pointer_cast<SlavableAutomationControl>(c);
679 
680 	if (sc) {
681 		sc->find_next_event (start, end, next_event);
682 	}
683 
684 	boost::shared_ptr<const Evoral::ControlList> alist (c->list());
685 	Evoral::ControlEvent cp (start, 0.0f);
686 	if (!alist) {
687 		return;
688 	}
689 
690 	Evoral::ControlList::const_iterator i = upper_bound (alist->begin(), alist->end(), &cp, Evoral::ControlList::time_comparator);
691 
692 	if (i != alist->end() && (*i)->when < end) {
693 		if ((*i)->when < next_event.when) {
694 			next_event.when = (*i)->when;
695 		}
696 	}
697 }
698 
699 void
find_prev_ac_event(boost::shared_ptr<AutomationControl> c,double start,double end,Evoral::ControlEvent & next_event) const700 Automatable::find_prev_ac_event (boost::shared_ptr<AutomationControl> c, double start, double end, Evoral::ControlEvent& next_event) const
701 {
702 	assert (start > end);
703 	boost::shared_ptr<SlavableAutomationControl> sc
704 		= boost::dynamic_pointer_cast<SlavableAutomationControl>(c);
705 
706 	if (sc) {
707 		sc->find_next_event (start, end, next_event);
708 	}
709 
710 	boost::shared_ptr<const Evoral::ControlList> alist (c->list());
711 	if (!alist) {
712 		return;
713 	}
714 
715 	Evoral::ControlEvent cp (end, 0.0f);
716 	Evoral::ControlList::const_iterator i = upper_bound (alist->begin(), alist->end(), &cp, Evoral::ControlList::time_comparator);
717 
718 	while (i != alist->end() && (*i)->when < start) {
719 		if ((*i)->when > next_event.when) {
720 			next_event.when = (*i)->when;
721 		}
722 		++i;
723 	}
724 }
725