7deeac422185e37c44684c5b680d917431d6aae6
[ardour.git] / libs / ardour / automation_control.cc
1 /*
2     Copyright (C) 2007 Paul Davis
3     Author: David Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <math.h>
22 #include <iostream>
23
24 #include "pbd/memento_command.h"
25 #include "pbd/stacktrace.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
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
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
81 AutomationControl::session_going_away ()
82 {
83         SessionHandleRef::session_going_away ();
84         DropReferences (); /* EMIT SIGNAL */
85         _no_session = true;
86 }
87
88 bool
89 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
100 AutomationControl::get_value() const
101 {
102         bool from_list = alist() && alist()->automation_playback();
103         return Control::get_double (from_list, _session.transport_frame());
104 }
105
106 double
107 AutomationControl::get_save_value() const
108 {
109         /* save user-value, not incl masters */
110         return Control::get_double ();
111 }
112
113 void
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
124 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
125 {
126         if (!writable()) {
127                 return;
128         }
129
130         /* enforce strict double/boolean value mapping */
131
132         if (_desc.toggled) {
133                 if (val != 0.0) {
134                         val = 1.0;
135                 }
136         }
137
138         if (check_rt (val, gcd)) {
139                 /* change has been queued to take place in an RT context */
140                 return;
141         }
142
143         if (_group && _group->use_me (gcd)) {
144                 _group->set_group_value (shared_from_this(), val);
145         } else {
146                 actually_set_value (val, gcd);
147         }
148 }
149
150 ControlList
151 AutomationControl::grouped_controls () const
152 {
153         if (_group && _group->use_me (PBD::Controllable::UseGroup)) {
154                 return _group->controls ();
155         } else {
156                 return ControlList ();
157         }
158 }
159
160 void
161 AutomationControl::automation_run (framepos_t start, pframes_t nframes)
162 {
163         if (!automation_playback ()) {
164                 return;
165         }
166
167         assert (_list);
168         bool valid = false;
169         double val = _list->rt_safe_eval (start, valid);
170         if (!valid) {
171                 return;
172         }
173         if (toggled ()) {
174                 const double thresh = .5 * (_desc.upper - _desc.lower);
175                 set_value_unchecked (val >= thresh ? _desc.upper : _desc.lower);
176         } else {
177                 set_value_unchecked (val);
178         }
179 }
180
181 /** Set the value and do the right thing based on automation state
182  *  (e.g. record if necessary, etc.)
183  *  @param value `user' value
184  */
185 void
186 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
187 {
188         boost::shared_ptr<AutomationList> al = alist ();
189         const framepos_t pos = _session.transport_frame();
190         bool to_list;
191
192         /* We cannot use ::get_value() here since that is virtual, and intended
193            to return a scalar value that in some way reflects the state of the
194            control (with semantics defined by the control itself, since it's
195            internal state may be more complex than can be fully represented by
196            a single scalar).
197
198            This method's only job is to set the "user_double()" value of the
199            underlying Evoral::Control object, and so we should compare the new
200            value we're being given to the current user_double().
201
202            Unless ... we're doing automation playback, in which case the
203            current effective value of the control (used to determine if
204            anything has changed) is the one derived from the automation event
205            list.
206         */
207         float old_value = Control::user_double();
208
209         if (al && al->automation_write ()) {
210                 to_list = true;
211         } else {
212                 to_list = false;
213         }
214
215         Control::set_double (value, pos, to_list);
216
217         if (old_value != (float)value) {
218 #if 0
219                 AutomationType at = (AutomationType) _parameter.type();
220                 std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
221                 << " (was " << old_value << ") @ " << this << std::endl;
222 #endif
223
224                 Changed (true, gcd);
225                 if (!al || !al->automation_playback ()) {
226                         _session.set_dirty ();
227                 }
228         }
229 }
230
231 void
232 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
233 {
234         Control::set_list (list);
235         Changed (true, Controllable::NoGroup);
236 }
237
238 void
239 AutomationControl::set_automation_state (AutoState as)
240 {
241         if (flags() & NotAutomatable) {
242                 return;
243         }
244         if (alist() && as != alist()->automation_state()) {
245
246                 const double val = get_value ();
247
248                 alist()->set_automation_state (as);
249                 if (_desc.toggled) {
250                         Changed (false, Controllable::NoGroup); // notify slaves, update boolean masters
251                         return;  // No watch for boolean automation
252                 }
253
254                 if (as == Write) {
255                         AutomationWatch::instance().add_automation_watch (shared_from_this());
256                 } else if (as == Touch) {
257                         if (alist()->empty()) {
258                                 Control::set_double (val, _session.current_start_frame (), true);
259                                 Control::set_double (val, _session.current_end_frame (), true);
260                                 Changed (true, Controllable::NoGroup);
261                         }
262                         if (!touching()) {
263                                 AutomationWatch::instance().remove_automation_watch (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 (shared_from_this());
271                         }
272                 } else {
273                         AutomationWatch::instance().remove_automation_watch (shared_from_this());
274                         Changed (false, Controllable::NoGroup);
275                 }
276         }
277 }
278
279 void
280 AutomationControl::start_touch (double when)
281 {
282         if (!_list || touching ()) {
283                 return;
284         }
285
286         if (alist()->automation_state() == Touch) {
287                 /* subtle. aligns the user value with the playback and
288                  * use take actual value (incl masters).
289                  *
290                  * Touch + hold writes inverse curve of master-automation
291                  * using AutomationWatch::timer ()
292                  */
293                 AutomationControl::actually_set_value (get_value (), Controllable::NoGroup);
294                 alist()->start_touch (when);
295                 if (!_desc.toggled) {
296                         AutomationWatch::instance().add_automation_watch (shared_from_this());
297                 }
298                 set_touching (true);
299         }
300 }
301
302 void
303 AutomationControl::stop_touch (double when)
304 {
305         if (!_list || !touching ()) {
306                 return;
307         }
308
309         set_touching (false);
310
311         if (alist()->automation_state() == Touch) {
312                 alist()->stop_touch (when);
313                 if (!_desc.toggled) {
314                         AutomationWatch::instance().remove_automation_watch (shared_from_this());
315                 }
316         }
317 }
318
319 void
320 AutomationControl::commit_transaction (bool did_write)
321 {
322         if (did_write) {
323                 XMLNode* before = alist ()->before ();
324                 if (before) {
325                         _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
326                         _session.commit_reversible_command (alist ()->memento_command (before, &alist ()->get_state ()));
327                 }
328         } else {
329                 alist ()->clear_history ();
330         }
331 }
332
333 /* take control-value and return UI range [0..1] */
334 double
335 AutomationControl::internal_to_interface (double val) const
336 {
337         // XXX maybe optimize. _desc.from_interface() has
338         // a switch-statement depending on AutomationType.
339         return _desc.to_interface (val);
340 }
341
342 /* map GUI range [0..1] to control-value */
343 double
344 AutomationControl::interface_to_internal (double val) const
345 {
346         if (!isfinite_local (val)) {
347                 assert (0);
348                 val = 0;
349         }
350         // XXX maybe optimize. see above.
351         return _desc.from_interface (val);
352 }
353
354 std::string
355 AutomationControl::get_user_string () const
356 {
357         return ARDOUR::value_as_string (_desc, get_value());
358 }
359
360 void
361 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
362 {
363         /* this method can only be called by a ControlGroup. We do not need
364            to ensure consistency by calling ControlGroup::remove_control(),
365            since we are guaranteed that the ControlGroup will take care of that
366            for us.
367         */
368
369         _group = cg;
370 }
371
372 bool
373 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
374 {
375         if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
376                 /* queue change in RT context */
377                 _session.set_control (shared_from_this(), val, gcd);
378                 return true;
379         }
380
381         return false;
382 }