Collect plugin runtime profile statistics.
[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_sample());
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 (samplepos_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 samplepos_t pos = _session.transport_sample();
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 | 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 (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 | Latch)) {
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         if (alist()->automation_state() == Latch && _session.transport_rolling ()) {
310                 return;
311         }
312
313         set_touching (false);
314
315         if (alist()->automation_state() & (Touch | Latch)) {
316                 alist()->stop_touch (when);
317                 if (!_desc.toggled) {
318                         AutomationWatch::instance().remove_automation_watch (shared_from_this());
319                 }
320         }
321 }
322
323 void
324 AutomationControl::commit_transaction (bool did_write)
325 {
326         if (did_write) {
327                 XMLNode* before = alist ()->before ();
328                 if (before) {
329                         _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
330                         _session.commit_reversible_command (alist ()->memento_command (before, &alist ()->get_state ()));
331                 }
332         } else {
333                 alist ()->clear_history ();
334         }
335 }
336
337 /* take control-value and return UI range [0..1] */
338 double
339 AutomationControl::internal_to_interface (double val) const
340 {
341         // XXX maybe optimize. _desc.from_interface() has
342         // a switch-statement depending on AutomationType.
343         return _desc.to_interface (val);
344 }
345
346 /* map GUI range [0..1] to control-value */
347 double
348 AutomationControl::interface_to_internal (double val) const
349 {
350         if (!isfinite_local (val)) {
351                 assert (0);
352                 val = 0;
353         }
354         // XXX maybe optimize. see above.
355         return _desc.from_interface (val);
356 }
357
358 std::string
359 AutomationControl::get_user_string () const
360 {
361         return ARDOUR::value_as_string (_desc, get_value());
362 }
363
364 void
365 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
366 {
367         /* this method can only be called by a ControlGroup. We do not need
368            to ensure consistency by calling ControlGroup::remove_control(),
369            since we are guaranteed that the ControlGroup will take care of that
370            for us.
371         */
372
373         _group = cg;
374 }
375
376 bool
377 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
378 {
379         if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
380                 /* queue change in RT context */
381                 _session.set_control (shared_from_this(), val, gcd);
382                 return true;
383         }
384
385         return false;
386 }