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, ¶m) != 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