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