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