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