2 * Copyright (C) 2007-2014 David Robillard <d@drobilla.net>
3 * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
5 * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "pbd/memento_command.h"
26 #include "pbd/stacktrace.h"
28 #include "ardour/audioengine.h"
29 #include "ardour/automation_control.h"
30 #include "ardour/automation_watch.h"
31 #include "ardour/control_group.h"
32 #include "ardour/event_type_map.h"
33 #include "ardour/session.h"
34 #include "ardour/selection.h"
35 #include "ardour/value_as_string.h"
41 // C99 'isfinite()' is not available in MSVC.
42 #define isfinite_local(val) (bool)_finite((double)val)
44 #define isfinite_local isfinite
48 using namespace ARDOUR;
51 AutomationControl::AutomationControl(ARDOUR::Session& session,
52 const Evoral::Parameter& parameter,
53 const ParameterDescriptor& desc,
54 boost::shared_ptr<ARDOUR::AutomationList> list,
56 Controllable::Flag flags)
58 : Controllable (name.empty() ? EventTypeMap::instance().to_symbol(parameter) : name, flags)
59 , Evoral::Control(parameter, desc, list)
60 , SessionHandleRef (session)
65 set_flags (Controllable::Toggle);
67 boost::shared_ptr<AutomationList> al = alist();
69 al->StateChanged.connect_same_thread (_state_changed_connection, boost::bind (&Session::set_dirty, &_session));
73 AutomationControl::~AutomationControl ()
75 if (!_no_session && !_session.deletion_in_progress ()) {
76 _session.selection().remove_control_by_id (id());
77 DropReferences (); /* EMIT SIGNAL */
82 AutomationControl::session_going_away ()
84 SessionHandleRef::session_going_away ();
85 DropReferences (); /* EMIT SIGNAL */
90 AutomationControl::writable() const
92 boost::shared_ptr<AutomationList> al = alist();
94 return al->automation_state() != Play;
99 /** Get the current effective `user' value based on automation state */
101 AutomationControl::get_value() const
103 bool from_list = alist() && alist()->automation_playback();
104 return Control::get_double (from_list, _session.transport_sample());
108 AutomationControl::get_save_value() const
110 /* save user-value, not incl masters */
111 return Control::get_double ();
115 AutomationControl::pre_realtime_queue_stuff (double val, PBD::Controllable::GroupControlDisposition gcd)
117 if (_group && _group->use_me (gcd)) {
118 _group->pre_realtime_queue_stuff (val);
120 do_pre_realtime_queue_stuff (val);
125 AutomationControl::set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
131 /* enforce strict double/boolean value mapping */
139 if (check_rt (val, gcd)) {
140 /* change has been queued to take place in an RT context */
144 if (_group && _group->use_me (gcd)) {
145 _group->set_group_value (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()), val);
147 actually_set_value (val, gcd);
152 AutomationControl::grouped_controls () const
154 if (_group && _group->use_me (PBD::Controllable::UseGroup)) {
155 return _group->controls ();
157 return ControlList ();
162 AutomationControl::automation_run (samplepos_t start, pframes_t nframes)
164 if (!automation_playback ()) {
170 double val = _list->rt_safe_eval (start, valid);
175 const double thresh = .5 * (_desc.upper - _desc.lower);
176 set_value_unchecked (val >= thresh ? _desc.upper : _desc.lower);
178 set_value_unchecked (val);
182 /** Set the value and do the right thing based on automation state
183 * (e.g. record if necessary, etc.)
184 * @param value `user' value
187 AutomationControl::actually_set_value (double value, PBD::Controllable::GroupControlDisposition gcd)
189 boost::shared_ptr<AutomationList> al = alist ();
190 const samplepos_t pos = _session.transport_sample();
193 /* We cannot use ::get_value() here since that is virtual, and intended
194 to return a scalar value that in some way reflects the state of the
195 control (with semantics defined by the control itself, since it's
196 internal state may be more complex than can be fully represented by
199 This method's only job is to set the "user_double()" value of the
200 underlying Evoral::Control object, and so we should compare the new
201 value we're being given to the current user_double().
203 Unless ... we're doing automation playback, in which case the
204 current effective value of the control (used to determine if
205 anything has changed) is the one derived from the automation event
208 float old_value = Control::user_double();
210 if (al && al->automation_write ()) {
216 Control::set_double (value, pos, to_list);
218 if (old_value != (float)value) {
220 AutomationType at = (AutomationType) _parameter.type();
221 std::cerr << "++++ Changed (" << enum_2_string (at) << ", " << enum_2_string (gcd) << ") = " << value
222 << " (was " << old_value << ") @ " << this << std::endl;
226 if (!al || !al->automation_playback ()) {
227 _session.set_dirty ();
233 AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
235 Control::set_list (list);
236 Changed (true, Controllable::NoGroup);
240 AutomationControl::set_automation_state (AutoState as)
242 if (flags() & NotAutomatable) {
245 if (alist() && as != alist()->automation_state()) {
247 const double val = get_value ();
249 alist()->set_automation_state (as);
251 Changed (false, Controllable::NoGroup); // notify slaves, update boolean masters
252 return; // No watch for boolean automation
256 AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
257 } else if (as & (Touch | Latch)) {
258 if (alist()->empty()) {
259 Control::set_double (val, _session.current_start_sample (), true);
260 Control::set_double (val, _session.current_end_sample (), true);
261 Changed (true, Controllable::NoGroup);
264 AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
266 /* this seems unlikely, but the combination of
267 * a control surface and the mouse could make
268 * it possible to put the control into Touch
269 * mode *while* touching it.
271 AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
274 AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
275 Changed (false, Controllable::NoGroup);
281 AutomationControl::start_touch (double when)
283 if (!_list || touching ()) {
287 if (alist()->automation_state() & (Touch | Latch)) {
288 /* subtle. aligns the user value with the playback and
289 * use take actual value (incl masters).
291 * Touch + hold writes inverse curve of master-automation
292 * using AutomationWatch::timer ()
294 AutomationControl::actually_set_value (get_value (), Controllable::NoGroup);
295 alist()->start_touch (when);
296 if (!_desc.toggled) {
297 AutomationWatch::instance().add_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
304 AutomationControl::stop_touch (double when)
306 if (!_list || !touching ()) {
310 if (alist()->automation_state() == Latch && _session.transport_rolling ()) {
314 set_touching (false);
316 if (alist()->automation_state() & (Touch | Latch)) {
317 alist()->stop_touch (when);
318 if (!_desc.toggled) {
319 AutomationWatch::instance().remove_automation_watch (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()));
325 AutomationControl::commit_transaction (bool did_write)
328 XMLNode* before = alist ()->before ();
330 _session.begin_reversible_command (string_compose (_("record %1 automation"), name ()));
331 _session.commit_reversible_command (alist ()->memento_command (before, &alist ()->get_state ()));
334 alist ()->clear_history ();
338 /* take control-value and return UI range [0..1] */
340 AutomationControl::internal_to_interface (double val, bool rotary) const
342 // XXX maybe optimize. _desc.from_interface() has
343 // a switch-statement depending on AutomationType.
344 return _desc.to_interface (val, rotary);
347 /* map GUI range [0..1] to control-value */
349 AutomationControl::interface_to_internal (double val, bool rotary) const
351 if (!isfinite_local (val)) {
355 // XXX maybe optimize. see above.
356 return _desc.from_interface (val, rotary);
360 AutomationControl::get_user_string () const
362 return ARDOUR::value_as_string (_desc, get_value());
366 AutomationControl::set_group (boost::shared_ptr<ControlGroup> cg)
368 /* this method can only be called by a ControlGroup. We do not need
369 to ensure consistency by calling ControlGroup::remove_control(),
370 since we are guaranteed that the ControlGroup will take care of that
378 AutomationControl::check_rt (double val, Controllable::GroupControlDisposition gcd)
380 if (!_session.loading() && (flags() & Controllable::RealTime) && !AudioEngine::instance()->in_process_thread()) {
381 /* queue change in RT context */
382 _session.set_control (boost::dynamic_pointer_cast<AutomationControl>(shared_from_this()), val, gcd);