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