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