1 /*
2  * Copyright (C) 2007-2014 David Robillard <d@drobilla.net>
3  * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
5  * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include <math.h>
23 #include <iostream>
24 
25 #include "pbd/memento_command.h"
26 
27 #include "ardour/audioengine.h"
28 #include "ardour/automation_control.h"
29 #include "ardour/automation_watch.h"
30 #include "ardour/control_group.h"
31 #include "ardour/event_type_map.h"
32 #include "ardour/session.h"
33 #include "ardour/selection.h"
34 #include "ardour/value_as_string.h"
35 
36 #include "pbd/i18n.h"
37 
38 #ifdef COMPILER_MSVC
39 #include <float.h>
40 // C99 'isfinite()' is not available in MSVC.
41 #define isfinite_local(val) (bool)_finite((double)val)
42 #else
43 #define isfinite_local isfinite
44 #endif
45 
46 using namespace std;
47 using namespace ARDOUR;
48 using namespace PBD;
49 
AutomationControl(ARDOUR::Session & session,const Evoral::Parameter & parameter,const ParameterDescriptor & desc,boost::shared_ptr<ARDOUR::AutomationList> list,const string & name,Controllable::Flag flags)50 AutomationControl::AutomationControl(ARDOUR::Session&                          session,
51                                      const Evoral::Parameter&                  parameter,
52                                      const ParameterDescriptor&                desc,
53                                      boost::shared_ptr<ARDOUR::AutomationList> list,
54                                      const string&                             name,
55                                      Controllable::Flag                        flags)
56 
57 	: Controllable (name.empty() ? EventTypeMap::instance().to_symbol(parameter) : name, flags)
58 	, Evoral::Control(parameter, desc, list)
59 	, SessionHandleRef (session)
60 	, _desc(desc)
61 	, _no_session(false)
62 {
63 	if (_desc.toggled) {
64 		set_flags (Controllable::Toggle);
65 	}
66 	boost::shared_ptr<AutomationList> al = alist();
67 	if (al) {
68 		al->StateChanged.connect_same_thread (_state_changed_connection, boost::bind (&Session::set_dirty, &_session));
69 	}
70 }
71 
~AutomationControl()72 AutomationControl::~AutomationControl ()
73 {
74 	if (!_no_session && !_session.deletion_in_progress ()) {
75 		_session.selection().remove_control_by_id (id());
76 		DropReferences (); /* EMIT SIGNAL */
77 	}
78 }
79 
80 void
session_going_away()81 AutomationControl::session_going_away ()
82 {
83 	SessionHandleRef::session_going_away ();
84 	DropReferences (); /* EMIT SIGNAL */
85 	_no_session = true;
86 }
87 
88 bool
writable() const89 AutomationControl::writable() const
90 {
91 	boost::shared_ptr<AutomationList> al = alist();
92 	if (al) {
93 		return al->automation_state() != Play;
94 	}
95 	return true;
96 }
97 
98 /** Get the current effective `user' value based on automation state */
99 double
get_value() const100 AutomationControl::get_value() const
101 {
102 	bool from_list = alist() && alist()->automation_playback();
103 	return Control::get_double (from_list, _session.transport_sample());
104 }
105 
106 double
get_save_value() const107 AutomationControl::get_save_value() const
108 {
109 	/* save user-value, not incl masters */
110 	return Control::get_double ();
111 }
112 
113 void
pre_realtime_queue_stuff(double val,PBD::Controllable::GroupControlDisposition gcd)114 AutomationControl::pre_realtime_queue_stuff (double val, PBD::Controllable::GroupControlDisposition gcd)
115 {
116 	if (_group && _group->use_me (gcd)) {
117 		_group->pre_realtime_queue_stuff (val);
118 	} else {
119 		do_pre_realtime_queue_stuff (val);
120 	}
121 }
122 
123 void
set_value(double val,PBD::Controllable::GroupControlDisposition gcd)124 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
125 {
126 	if (!writable()) {
127 		return;
128 	}
129 
130 	if (_list && !touching () && alist()->automation_state() == Latch && _session.transport_rolling ()) {
131 		start_touch (_session.transport_sample ());
132 	}
133 
134 	/* enforce strict double/boolean value mapping */
135 
136 	if (_desc.toggled) {
137 		if (val != 0.0) {
138 			val = 1.0;
139 		}
140 	}
141 
142 	if (check_rt (val, gcd)) {
143 		/* change has been queued to take place in an RT context */
144 		return;
145 	}
146 
147 	if (_group && _group->use_me (gcd)) {
148 		_group->set_group_value (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()), val);
149 	} else {
150 		actually_set_value (val, gcd);
151 	}
152 }
153 
154 ControlList
grouped_controls() const155 AutomationControl::grouped_controls () const
156 {
157 	if (_group && _group->use_me (PBD::Controllable::UseGroup)) {
158 		return _group->controls ();
159 	} else {
160 		return ControlList ();
161 	}
162 }
163 
164 void
automation_run(samplepos_t start,pframes_t nframes)165 AutomationControl::automation_run (samplepos_t start, pframes_t nframes)
166 {
167 	if (!automation_playback ()) {
168 		return;
169 	}
170 
171 	assert (_list);
172 	bool valid = false;
173 	double val = _list->rt_safe_eval (start, valid);
174 	if (!valid) {
175 		return;
176 	}
177 	if (toggled ()) {
178 		const double thresh = .5 * (_desc.upper - _desc.lower);
179 		set_value_unchecked (val >= thresh ? _desc.upper : _desc.lower);
180 	} else {
181 		set_value_unchecked (val);
182 	}
183 }
184 
185 /** Set the value and do the right thing based on automation state
186  *  (e.g. record if necessary, etc.)
187  *  @param value `user' value
188  */
189 void
actually_set_value(double value,PBD::Controllable::GroupControlDisposition gcd)190 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
191 {
192 	boost::shared_ptr<AutomationList> al = alist ();
193 	const samplepos_t pos = _session.transport_sample();
194 	bool to_list;
195 
196 	/* We cannot use ::get_value() here since that is virtual, and intended
197 	   to return a scalar value that in some way reflects the state of the
198 	   control (with semantics defined by the control itself, since it's
199 	   internal state may be more complex than can be fully represented by
200 	   a single scalar).
201 
202 	   This method's only job is to set the "user_double()" value of the
203 	   underlying Evoral::Control object, and so we should compare the new
204 	   value we're being given to the current user_double().
205 
206 	   Unless ... we're doing automation playback, in which case the
207 	   current effective value of the control (used to determine if
208 	   anything has changed) is the one derived from the automation event
209 	   list.
210 	*/
211 	float old_value = Control::user_double();
212 
213 	if (al && al->automation_write ()) {
214 		to_list = true;
215 	} else {
216 		to_list = false;
217 	}
218 
219 	Control::set_double (value, pos, to_list);
220 
221 	if (old_value != (float)value) {
222 #if 0
223 		AutomationType at = (AutomationType) _parameter.type();
224 		std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
225 		<< " (was " << old_value << ") @ " << this << std::endl;
226 #endif
227 
228 		Changed (true, gcd);
229 		if (!al || !al->automation_playback ()) {
230 			_session.set_dirty ();
231 		}
232 	}
233 }
234 
235 void
set_list(boost::shared_ptr<Evoral::ControlList> list)236 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
237 {
238 	Control::set_list (list);
239 	Changed (true, Controllable::NoGroup);
240 }
241 
242 void
set_automation_state(AutoState as)243 AutomationControl::set_automation_state (AutoState as)
244 {
245 	if (flags() & NotAutomatable) {
246 		return;
247 	}
248 	if (alist() && as != alist()->automation_state()) {
249 
250 		const double val = get_value ();
251 
252 		alist()->set_automation_state (as);
253 
254 		if (as == Write) {
255 			AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
256 		} else if (as & (Touch | Latch)) {
257 			if (alist()->empty()) {
258 				Control::set_double (val, _session.current_start_sample (), true);
259 				Control::set_double (val, _session.current_end_sample (), true);
260 				Changed (true, Controllable::NoGroup);
261 			}
262 			if (!touching()) {
263 				AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
264 			} else {
265 				/* this seems unlikely, but the combination of
266 				 * a control surface and the mouse could make
267 				 * it possible to put the control into Touch
268 				 * mode *while* touching it.
269 				 */
270 				AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
271 			}
272 		} else {
273 			AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
274 			Changed (false, Controllable::NoGroup);
275 		}
276 	}
277 }
278 
279 void
start_touch(double when)280 AutomationControl::start_touch (double when)
281 {
282 	if (!_list || touching ()) {
283 		return;
284 	}
285 
286 	ControlTouched (boost::dynamic_pointer_cast<PBD::Controllable>(shared_from_this())); /* EMIT SIGNAL */
287 
288 	if (alist()->automation_state() & (Touch | Latch)) {
289 		/* subtle. aligns the user value with the playback and
290 		 * use take actual value (incl masters).
291 		 *
292 		 * Touch + hold writes inverse curve of master-automation
293 		 * using AutomationWatch::timer ()
294 		 */
295 		AutomationControl::actually_set_value (get_value (), Controllable::NoGroup);
296 		alist()->start_touch (when);
297 		AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
298 		set_touching (true);
299 	}
300 }
301 
302 void
stop_touch(double when)303 AutomationControl::stop_touch (double when)
304 {
305 	if (!_list || !touching ()) {
306 		return;
307 	}
308 
309 	if (alist()->automation_state() == Latch && _session.transport_rolling ()) {
310 		return;
311 	}
312 	if (alist()->automation_state() == Touch && _session.transport_rolling () && _desc.toggled) {
313 		/* Toggle buttons always latch */
314 		return;
315 	}
316 
317 	set_touching (false);
318 
319 	if (alist()->automation_state() & (Touch | Latch)) {
320 		alist()->stop_touch (when);
321 		AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
322 	}
323 }
324 
325 void
commit_transaction(bool did_write)326 AutomationControl::commit_transaction (bool did_write)
327 {
328 	if (did_write) {
329 		XMLNode* before = alist ()->before ();
330 		if (before) {
331 			_session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
332 			_session.commit_reversible_command (alist ()->memento_command (before, &alist ()->get_state ()));
333 		}
334 	} else {
335 		alist ()->clear_history ();
336 	}
337 }
338 
339 /* take control-value and return UI range [0..1] */
340 double
internal_to_interface(double val,bool rotary) const341 AutomationControl::internal_to_interface (double val, bool rotary) const
342 {
343 	// XXX maybe optimize. _desc.from_interface() has
344 	// a switch-statement depending on AutomationType.
345 	return _desc.to_interface (val, rotary);
346 }
347 
348 /* map GUI range [0..1] to control-value */
349 double
interface_to_internal(double val,bool rotary) const350 AutomationControl::interface_to_internal (double val, bool rotary) const
351 {
352 	if (!isfinite_local (val)) {
353 		assert (0);
354 		val = 0;
355 	}
356 	// XXX maybe optimize. see above.
357 	return _desc.from_interface (val, rotary);
358 }
359 
360 std::string
get_user_string() const361 AutomationControl::get_user_string () const
362 {
363 	return ARDOUR::value_as_string (_desc, get_value());
364 }
365 
366 void
set_group(boost::shared_ptr<ControlGroup> cg)367 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
368 {
369 	/* this method can only be called by a ControlGroup. We do not need
370 	   to ensure consistency by calling ControlGroup::remove_control(),
371 	   since we are guaranteed that the ControlGroup will take care of that
372 	   for us.
373 	*/
374 
375 	_group = cg;
376 }
377 
378 bool
check_rt(double val,Controllable::GroupControlDisposition gcd)379 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
380 {
381 	if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
382 		/* queue change in RT context */
383 		_session.set_control (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()), val, gcd);
384 		return true;
385 	}
386 
387 	return false;
388 }
389