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)
63 set_flags (Controllable::Toggle);
65 boost::shared_ptr<AutomationList> al = alist();
67 al->StateChanged.connect_same_thread (_state_changed_connection, boost::bind (&Session::set_dirty, &_session));
71 AutomationControl::~AutomationControl ()
73 _session.selection().remove_control_by_id (id());
74 DropReferences (); /* EMIT SIGNAL */
78 AutomationControl::writable() const
80 boost::shared_ptr<AutomationList> al = alist();
82 return al->automation_state() != Play;
87 /** Get the current effective `user' value based on automation state */
89 AutomationControl::get_value() const
91 bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
92 return Control::get_double (from_list, _session.transport_frame());
96 AutomationControl::get_save_value() const
98 /* save user-value, not incl masters */
99 return Control::get_double ();
103 AutomationControl::pre_realtime_queue_stuff (double val, PBD::Controllable::GroupControlDisposition gcd)
105 if (_group && _group->use_me (gcd)) {
106 _group->pre_realtime_queue_stuff (val);
108 do_pre_realtime_queue_stuff (val);
113 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
119 /* enforce strict double/boolean value mapping */
127 if (check_rt (val, gcd)) {
128 /* change has been queued to take place in an RT context */
132 if (_group && _group->use_me (gcd)) {
133 _group->set_group_value (shared_from_this(), val);
135 actually_set_value (val, gcd);
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
144 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
146 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (_list);
147 const framepos_t pos = _session.transport_frame();
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
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().
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
169 old_value = Control::user_double();
171 if (al->automation_write ()) {
173 old_value = Control::user_double ();
174 } else if (al->automation_playback()) {
176 old_value = al->eval (pos);
179 old_value = Control::user_double ();
183 Control::set_double (value, pos, to_list);
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;
191 if (!al || !al->automation_playback ()) {
192 _session.set_dirty ();
199 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
201 Control::set_list (list);
202 Changed (true, Controllable::NoGroup);
206 AutomationControl::set_automation_state (AutoState as)
208 if (flags() & NotAutomatable) {
211 if (_list && as != alist()->automation_state()) {
213 const double val = get_value ();
215 alist()->set_automation_state (as);
217 Changed (false, Controllable::NoGroup); // notify slaves, update boolean masters
218 return; // No watch for boolean automation
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);
230 AutomationWatch::instance().remove_automation_watch (shared_from_this());
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.
237 AutomationWatch::instance().add_automation_watch (shared_from_this());
240 AutomationWatch::instance().remove_automation_watch (shared_from_this());
246 AutomationControl::set_automation_style (AutoStyle as)
249 alist()->set_automation_style (as);
253 AutomationControl::start_touch(double when)
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());
274 AutomationControl::stop_touch(bool mark, double when)
278 set_touching (false);
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());
291 AutomationControl::commit_transaction (bool 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 ()));
299 alist ()->clear_history ();
304 AutomationControl::internal_to_interface (double val) const
306 if (_desc.integer_step) {
307 // both upper and lower are inclusive.
308 val = (val - lower()) / (1 + upper() - lower());
310 val = (val - lower()) / (upper() - lower());
313 if (_desc.logarithmic) {
315 val = pow (val, 1./2.0);
325 AutomationControl::interface_to_internal (double val) const
327 if (!isfinite_local (val)) {
330 if (_desc.logarithmic) {
334 val = pow (val, 2.0);
338 if (_desc.integer_step) {
339 val = lower() + val * (1 + upper() - lower());
341 val = lower() + val * (upper() - lower());
344 if (val < lower()) val = lower();
345 if (val > upper()) val = upper();
351 AutomationControl::get_user_string () const
353 return ARDOUR::value_as_string (_desc, get_value());
357 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
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
369 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
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);