2 Copyright (C) 2007 Paul Davis
3 Author: David Robillard
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.
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.
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.
24 #include "pbd/memento_command.h"
25 #include "pbd/stacktrace.h"
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"
40 // C99 'isfinite()' is not available in MSVC.
41 #define isfinite_local(val) (bool)_finite((double)val)
43 #define isfinite_local isfinite
47 using namespace ARDOUR;
50 AutomationControl::AutomationControl(ARDOUR::Session& session,
51 const Evoral::Parameter& parameter,
52 const ParameterDescriptor& desc,
53 boost::shared_ptr<ARDOUR::AutomationList> list,
55 Controllable::Flag flags)
57 : Controllable (name.empty() ? EventTypeMap::instance().to_symbol(parameter) : name, flags)
58 , Evoral::Control(parameter, desc, list)
59 , SessionHandleRef (session)
64 set_flags (Controllable::Toggle);
66 boost::shared_ptr<AutomationList> al = alist();
68 al->StateChanged.connect_same_thread (_state_changed_connection, boost::bind (&Session::set_dirty, &_session));
72 AutomationControl::~AutomationControl ()
74 if (!_no_session && !_session.deletion_in_progress ()) {
75 _session.selection().remove_control_by_id (id());
76 DropReferences (); /* EMIT SIGNAL */
81 AutomationControl::session_going_away ()
83 SessionHandleRef::session_going_away ();
84 DropReferences (); /* EMIT SIGNAL */
89 AutomationControl::writable() const
91 boost::shared_ptr<AutomationList> al = alist();
93 return al->automation_state() != Play;
98 /** Get the current effective `user' value based on automation state */
100 AutomationControl::get_value() const
102 bool from_list = alist() && alist()->automation_playback();
103 return Control::get_double (from_list, _session.transport_frame());
107 AutomationControl::get_save_value() const
109 /* save user-value, not incl masters */
110 return Control::get_double ();
114 AutomationControl::pre_realtime_queue_stuff (double val, PBD::Controllable::GroupControlDisposition gcd)
116 if (_group && _group->use_me (gcd)) {
117 _group->pre_realtime_queue_stuff (val);
119 do_pre_realtime_queue_stuff (val);
124 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
130 /* enforce strict double/boolean value mapping */
138 if (check_rt (val, gcd)) {
139 /* change has been queued to take place in an RT context */
143 if (_group && _group->use_me (gcd)) {
144 _group->set_group_value (shared_from_this(), val);
146 actually_set_value (val, gcd);
151 AutomationControl::grouped_controls () const
153 if (_group && _group->use_me (PBD::Controllable::UseGroup)) {
154 return _group->controls ();
156 return ControlList ();
161 AutomationControl::automation_run (framepos_t start, pframes_t nframes)
163 if (!automation_playback ()) {
169 double val = _list->rt_safe_eval (start, valid);
174 const double thresh = .5 * (_desc.upper - _desc.lower);
175 set_value_unchecked (val >= thresh ? _desc.upper : _desc.lower);
177 set_value_unchecked (val);
181 /** Set the value and do the right thing based on automation state
182 * (e.g. record if necessary, etc.)
183 * @param value `user' value
186 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
188 boost::shared_ptr<AutomationList> al = alist ();
189 const framepos_t pos = _session.transport_frame();
192 /* We cannot use ::get_value() here since that is virtual, and intended
193 to return a scalar value that in some way reflects the state of the
194 control (with semantics defined by the control itself, since it's
195 internal state may be more complex than can be fully represented by
198 This method's only job is to set the "user_double()" value of the
199 underlying Evoral::Control object, and so we should compare the new
200 value we're being given to the current user_double().
202 Unless ... we're doing automation playback, in which case the
203 current effective value of the control (used to determine if
204 anything has changed) is the one derived from the automation event
207 float old_value = Control::user_double();
209 if (al && al->automation_write ()) {
215 Control::set_double (value, pos, to_list);
217 if (old_value != (float)value) {
219 AutomationType at = (AutomationType) _parameter.type();
220 std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
221 << " (was " << old_value << ") @ " << this << std::endl;
225 if (!al || !al->automation_playback ()) {
226 _session.set_dirty ();
232 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
234 Control::set_list (list);
235 Changed (true, Controllable::NoGroup);
239 AutomationControl::set_automation_state (AutoState as)
241 if (flags() & NotAutomatable) {
244 if (alist() && as != alist()->automation_state()) {
246 const double val = get_value ();
248 alist()->set_automation_state (as);
250 Changed (false, Controllable::NoGroup); // notify slaves, update boolean masters
251 return; // No watch for boolean automation
255 AutomationWatch::instance().add_automation_watch (shared_from_this());
256 } else if (as & (Touch | Latch)) {
257 if (alist()->empty()) {
258 Control::set_double (val, _session.current_start_frame (), true);
259 Control::set_double (val, _session.current_end_frame (), true);
260 Changed (true, Controllable::NoGroup);
263 AutomationWatch::instance().remove_automation_watch (shared_from_this());
265 /* this seems unlikely, but the combination of
266 * a control surface and the mouse could make
267 * it possible to put the control into Touch
268 * mode *while* touching it.
270 AutomationWatch::instance().add_automation_watch (shared_from_this());
273 AutomationWatch::instance().remove_automation_watch (shared_from_this());
274 Changed (false, Controllable::NoGroup);
280 AutomationControl::start_touch (double when)
282 if (!_list || touching ()) {
286 if (alist()->automation_state() & (Touch | Latch)) {
287 /* subtle. aligns the user value with the playback and
288 * use take actual value (incl masters).
290 * Touch + hold writes inverse curve of master-automation
291 * using AutomationWatch::timer ()
293 AutomationControl::actually_set_value (get_value (), Controllable::NoGroup);
294 alist()->start_touch (when);
295 if (!_desc.toggled) {
296 AutomationWatch::instance().add_automation_watch (shared_from_this());
303 AutomationControl::stop_touch (double when)
305 if (!_list || !touching ()) {
309 if (alist()->automation_state() == Latch && _session.transport_rolling ()) {
313 set_touching (false);
315 if (alist()->automation_state() & (Touch | Latch)) {
316 alist()->stop_touch (when);
317 if (!_desc.toggled) {
318 AutomationWatch::instance().remove_automation_watch (shared_from_this());
324 AutomationControl::commit_transaction (bool did_write)
327 XMLNode* before = alist ()->before ();
329 _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
330 _session.commit_reversible_command (alist ()->memento_command (before, &alist ()->get_state ()));
333 alist ()->clear_history ();
337 /* take control-value and return UI range [0..1] */
339 AutomationControl::internal_to_interface (double val) const
341 // XXX maybe optimize. _desc.from_interface() has
342 // a switch-statement depending on AutomationType.
343 return _desc.to_interface (val);
346 /* map GUI range [0..1] to control-value */
348 AutomationControl::interface_to_internal (double val) const
350 if (!isfinite_local (val)) {
354 // XXX maybe optimize. see above.
355 return _desc.from_interface (val);
359 AutomationControl::get_user_string () const
361 return ARDOUR::value_as_string (_desc, get_value());
365 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
367 /* this method can only be called by a ControlGroup. We do not need
368 to ensure consistency by calling ControlGroup::remove_control(),
369 since we are guaranteed that the ControlGroup will take care of that
377 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
379 if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
380 /* queue change in RT context */
381 _session.set_control (shared_from_this(), val, gcd);