VCA/SlavableAutomationCtrl re-design:
[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 /** Set the value and do the right thing based on automation state
140  *  (e.g. record if necessary, etc.)
141  *  @param value `user' value
142  */
143 void
144 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
145 {
146         boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (_list);
147         const framepos_t pos = _session.transport_frame();
148         bool to_list;
149         double old_value;
150
151         /* We cannot use ::get_value() here since that is virtual, and intended
152            to return a scalar value that in some way reflects the state of the
153            control (with semantics defined by the control itself, since it's
154            internal state may be more complex than can be fully represented by
155            a single scalar).
156
157            This method's only job is to set the "user_double()" value of the
158            underlying Evoral::Control object, and so we should compare the new
159            value we're being given to the current user_double().
160
161            Unless ... we're doing automation playback, in which case the
162            current effective value of the control (used to determine if
163            anything has changed) is the one derived from the automation event
164            list.
165         */
166
167         if (!al) {
168                 to_list = false;
169                 old_value = Control::user_double();
170         } else {
171                 if (al->automation_write ()) {
172                         to_list = true;
173                         old_value = Control::user_double ();
174                 } else if (al->automation_playback()) {
175                         to_list = false;
176                         old_value = al->eval (pos);
177                 } else {
178                         to_list = false;
179                         old_value = Control::user_double ();
180                 }
181         }
182
183         Control::set_double (value, pos, to_list);
184
185         if (old_value != value) {
186                 //AutomationType at = (AutomationType) _parameter.type();
187                 //std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
188                 //<< " (was " << old_value << ") @ " << this << std::endl;
189
190                 Changed (true, gcd);
191                 if (!al || !al->automation_playback ()) {
192                         _session.set_dirty ();
193                 }
194         }
195
196 }
197
198 void
199 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
200 {
201         Control::set_list (list);
202         Changed (true, Controllable::NoGroup);
203 }
204
205 void
206 AutomationControl::set_automation_state (AutoState as)
207 {
208         if (flags() & NotAutomatable) {
209                 return;
210         }
211         if (_list && as != alist()->automation_state()) {
212
213                 const double val = get_value ();
214
215                 alist()->set_automation_state (as);
216                 if (_desc.toggled) {
217                         Changed (false, Controllable::NoGroup); // notify slaves, update boolean masters
218                         return;  // No watch for boolean automation
219                 }
220
221                 if (as == Write) {
222                         AutomationWatch::instance().add_automation_watch (shared_from_this());
223                 } else if (as == Touch) {
224                         if (alist()->empty()) {
225                                 Control::set_double (val, _session.current_start_frame (), true);
226                                 Control::set_double (val, _session.current_end_frame (), true);
227                                 Changed (true, Controllable::NoGroup);
228                         }
229                         if (!touching()) {
230                                 AutomationWatch::instance().remove_automation_watch (shared_from_this());
231                         } else {
232                                 /* this seems unlikely, but the combination of
233                                  * a control surface and the mouse could make
234                                  * it possible to put the control into Touch
235                                  * mode *while* touching it.
236                                  */
237                                 AutomationWatch::instance().add_automation_watch (shared_from_this());
238                         }
239                 } else {
240                         AutomationWatch::instance().remove_automation_watch (shared_from_this());
241                 }
242         }
243 }
244
245 void
246 AutomationControl::set_automation_style (AutoStyle as)
247 {
248         if (!_list) return;
249         alist()->set_automation_style (as);
250 }
251
252 void
253 AutomationControl::start_touch(double when)
254 {
255         if (!_list) {
256                 return;
257         }
258
259         if (!touching()) {
260
261                 if (alist()->automation_state() == Touch) {
262                         /* subtle. aligns the user value with the playback */
263                         set_value (get_value (), Controllable::NoGroup);
264                         alist()->start_touch (when);
265                         if (!_desc.toggled) {
266                                 AutomationWatch::instance().add_automation_watch (shared_from_this());
267                         }
268                 }
269                 set_touching (true);
270         }
271 }
272
273 void
274 AutomationControl::stop_touch(bool mark, double when)
275 {
276         if (!_list) return;
277         if (touching()) {
278                 set_touching (false);
279
280                 if (alist()->automation_state() == Touch) {
281                         alist()->stop_touch (mark, when);
282                         if (!_desc.toggled) {
283                                 AutomationWatch::instance().remove_automation_watch (shared_from_this());
284
285                         }
286                 }
287         }
288 }
289
290 void
291 AutomationControl::commit_transaction (bool did_write)
292 {
293         if (did_write) {
294                 if (alist ()->before ()) {
295                         _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
296                         _session.commit_reversible_command (new MementoCommand<AutomationList> (*alist ().get (), alist ()->before (), &alist ()->get_state ()));
297                 }
298         } else {
299                 alist ()->clear_history ();
300         }
301 }
302
303 double
304 AutomationControl::internal_to_interface (double val) const
305 {
306         if (_desc.integer_step) {
307                 // both upper and lower are inclusive.
308                 val =  (val - lower()) / (1 + upper() - lower());
309         } else {
310                 val =  (val - lower()) / (upper() - lower());
311         }
312
313         if (_desc.logarithmic) {
314                 if (val > 0) {
315                         val = pow (val, 1./2.0);
316                 } else {
317                         val = 0;
318                 }
319         }
320
321         return val;
322 }
323
324 double
325 AutomationControl::interface_to_internal (double val) const
326 {
327         if (!isfinite_local (val)) {
328                 val = 0;
329         }
330         if (_desc.logarithmic) {
331                 if (val <= 0) {
332                         val = 0;
333                 } else {
334                         val = pow (val, 2.0);
335                 }
336         }
337
338         if (_desc.integer_step) {
339                 val =  lower() + val * (1 + upper() - lower());
340         } else {
341                 val =  lower() + val * (upper() - lower());
342         }
343
344         if (val < lower()) val = lower();
345         if (val > upper()) val = upper();
346
347         return val;
348 }
349
350 std::string
351 AutomationControl::get_user_string () const
352 {
353         return ARDOUR::value_as_string (_desc, get_value());
354 }
355
356 void
357 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
358 {
359         /* this method can only be called by a ControlGroup. We do not need
360            to ensure consistency by calling ControlGroup::remove_control(),
361            since we are guaranteed that the ControlGroup will take care of that
362            for us.
363         */
364
365         _group = cg;
366 }
367
368 bool
369 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
370 {
371         if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
372                 /* queue change in RT context */
373                 _session.set_control (shared_from_this(), val, gcd);
374                 return true;
375         }
376
377         return false;
378 }