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"
38 // C99 'isfinite()' is not available in MSVC.
39 #define isfinite_local(val) (bool)_finite((double)val)
41 #define isfinite_local isfinite
45 using namespace ARDOUR;
48 AutomationControl::AutomationControl(ARDOUR::Session& session,
49 const Evoral::Parameter& parameter,
50 const ParameterDescriptor& desc,
51 boost::shared_ptr<ARDOUR::AutomationList> list,
53 Controllable::Flag flags)
55 : Controllable (name.empty() ? EventTypeMap::instance().to_symbol(parameter) : name, flags)
56 , Evoral::Control(parameter, desc, list)
61 set_flags (Controllable::Toggle);
65 AutomationControl::~AutomationControl ()
67 DropReferences (); /* EMIT SIGNAL */
71 AutomationControl::writable() const
73 boost::shared_ptr<AutomationList> al = alist();
75 return al->automation_state() != Play;
80 /** Get the current effective `user' value based on automation state */
82 AutomationControl::get_value() const
84 bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
85 return Control::get_double (from_list, _session.transport_frame());
89 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
95 /* enforce strict double/boolean value mapping */
103 if (check_rt (val, gcd)) {
104 /* change has been queued to take place in an RT context */
108 if (_group && _group->use_me (gcd)) {
109 _group->set_group_value (shared_from_this(), val);
111 actually_set_value (val, gcd);
115 /** Set the value and do the right thing based on automation state
116 * (e.g. record if necessary, etc.)
117 * @param value `user' value
120 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
122 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (_list);
123 const framepos_t pos = _session.transport_frame();
127 /* We cannot use ::get_value() here since that is virtual, and intended
128 to return a scalar value that in some way reflects the state of the
129 control (with semantics defined by the control itself, since it's
130 internal state may be more complex than can be fully represented by
133 This method's only job is to set the "user_double()" value of the
134 underlying Evoral::Control object, and so we should compare the new
135 value we're being given to the current user_double().
137 Unless ... we're doing automation playback, in which case the
138 current effective value of the control (used to determine if
139 anything has changed) is the one derived from the automation event
145 old_value = Control::user_double();
147 if (al->automation_write ()) {
149 old_value = Control::user_double ();
150 } else if (al->automation_playback()) {
152 old_value = al->eval (pos);
155 old_value = Control::user_double ();
159 Control::set_double (value, pos, to_list);
161 if (old_value != value) {
162 // AutomationType at = (AutomationType) _parameter.type();
163 // std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
164 // << " (was " << old_value << ") @ " << this << std::endl;
167 _session.set_dirty ();
173 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
175 Control::set_list (list);
176 Changed (true, Controllable::NoGroup);
180 AutomationControl::set_automation_state (AutoState as)
182 if (flags() & NotAutomatable) {
185 if (_list && as != alist()->automation_state()) {
187 const double val = get_value ();
189 alist()->set_automation_state (as);
191 return; // No watch for boolean automation
195 AutomationWatch::instance().add_automation_watch (shared_from_this());
196 } else if (as == Touch) {
197 if (alist()->empty()) {
198 Control::set_double (val, _session.current_start_frame (), true);
199 Control::set_double (val, _session.current_end_frame (), true);
200 Changed (true, Controllable::NoGroup);
203 AutomationWatch::instance().remove_automation_watch (shared_from_this());
205 /* this seems unlikely, but the combination of
206 * a control surface and the mouse could make
207 * it possible to put the control into Touch
208 * mode *while* touching it.
210 AutomationWatch::instance().add_automation_watch (shared_from_this());
213 AutomationWatch::instance().remove_automation_watch (shared_from_this());
219 AutomationControl::set_automation_style (AutoStyle as)
222 alist()->set_automation_style (as);
226 AutomationControl::start_touch(double when)
234 if (alist()->automation_state() == Touch) {
235 /* subtle. aligns the user value with the playback */
236 set_value (get_value (), Controllable::NoGroup);
237 alist()->start_touch (when);
238 if (!_desc.toggled) {
239 AutomationWatch::instance().add_automation_watch (shared_from_this());
247 AutomationControl::stop_touch(bool mark, double when)
251 set_touching (false);
253 if (alist()->automation_state() == Touch) {
254 alist()->stop_touch (mark, when);
255 if (!_desc.toggled) {
256 AutomationWatch::instance().remove_automation_watch (shared_from_this());
264 AutomationControl::commit_transaction (bool did_write)
267 if (alist ()->before ()) {
268 _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
269 _session.commit_reversible_command (new MementoCommand<AutomationList> (*alist ().get (), alist ()->before (), &alist ()->get_state ()));
272 alist ()->clear_history ();
277 AutomationControl::internal_to_interface (double val) const
279 if (_desc.integer_step) {
280 // both upper and lower are inclusive.
281 val = (val - lower()) / (1 + upper() - lower());
283 val = (val - lower()) / (upper() - lower());
286 if (_desc.logarithmic) {
288 val = pow (val, 1./2.0);
298 AutomationControl::interface_to_internal (double val) const
300 if (!isfinite_local (val)) {
303 if (_desc.logarithmic) {
307 val = pow (val, 2.0);
311 if (_desc.integer_step) {
312 val = lower() + val * (1 + upper() - lower());
314 val = lower() + val * (upper() - lower());
317 if (val < lower()) val = lower();
318 if (val > upper()) val = upper();
324 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
326 /* this method can only be called by a ControlGroup. We do not need
327 to ensure consistency by calling ControlGroup::remove_control(),
328 since we are guaranteed that the ControlGroup will take care of that
336 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
338 if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
339 /* queue change in RT context */
340 _session.set_control (shared_from_this(), val, gcd);