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