Move panner bypass state up to the PannerShell so that it is preserved even when...
[ardour2.git] / libs / ardour / automatable.cc
blob6d8114939de2c3d8130aa5ae112575cdf4b9bd8c
1 /*
2 Copyright (C) 2001,2007 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include "ardour/ardour.h"
21 #include <fstream>
22 #include <inttypes.h>
23 #include <cstdio>
24 #include <errno.h>
26 #include <glibmm/miscutils.h>
28 #include "pbd/error.h"
29 #include "pbd/enumwriter.h"
30 #include "pbd/stacktrace.h"
32 #include "midi++/names.h"
34 #include "ardour/automatable.h"
35 #include "ardour/amp.h"
36 #include "ardour/event_type_map.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/pannable.h"
39 #include "ardour/panner.h"
40 #include "ardour/pan_controllable.h"
41 #include "ardour/plugin_insert.h"
42 #include "ardour/session.h"
44 #include "i18n.h"
46 using namespace std;
47 using namespace ARDOUR;
48 using namespace PBD;
50 framecnt_t Automatable::_automation_interval = 0;
51 const string Automatable::xml_node_name = X_("Automation");
53 Automatable::Automatable(Session& session)
54 : _a_session(session)
55 , _last_automation_snapshot(0)
59 Automatable::Automatable (const Automatable& other)
60 : ControlSet (other)
61 , _a_session (other._a_session)
62 , _last_automation_snapshot (0)
64 Glib::Mutex::Lock lm (other._control_lock);
66 for (Controls::const_iterator i = other._controls.begin(); i != other._controls.end(); ++i) {
67 boost::shared_ptr<Evoral::Control> ac (control_factory (i->first));
68 add_control (ac);
71 int
72 Automatable::old_set_automation_state (const XMLNode& node)
74 const XMLProperty *prop;
76 if ((prop = node.property ("path")) != 0) {
77 load_automation (prop->value());
78 } else {
79 warning << _("Automation node has no path property") << endmsg;
82 _last_automation_snapshot = 0;
84 return 0;
87 int
88 Automatable::load_automation (const string& path)
90 string fullpath;
92 if (Glib::path_is_absolute (path)) { // legacy
93 fullpath = path;
94 } else {
95 fullpath = _a_session.automation_dir();
96 fullpath += path;
98 ifstream in (fullpath.c_str());
100 if (!in) {
101 warning << string_compose(_("cannot open %2 to load automation data (%3)")
102 , fullpath, strerror (errno)) << endmsg;
103 return 1;
106 Glib::Mutex::Lock lm (control_lock());
107 set<Evoral::Parameter> tosave;
108 controls().clear ();
110 _last_automation_snapshot = 0;
112 while (in) {
113 double when;
114 double value;
115 uint32_t port;
117 in >> port; if (!in) break;
118 in >> when; if (!in) goto bad;
119 in >> value; if (!in) goto bad;
121 Evoral::Parameter param(PluginAutomation, 0, port);
122 /* FIXME: this is legacy and only used for plugin inserts? I think? */
123 boost::shared_ptr<Evoral::Control> c = control (param, true);
124 c->list()->add (when, value);
125 tosave.insert (param);
128 return 0;
130 bad:
131 error << string_compose(_("cannot load automation data from %2"), fullpath) << endmsg;
132 controls().clear ();
133 return -1;
136 void
137 Automatable::add_control(boost::shared_ptr<Evoral::Control> ac)
139 Evoral::Parameter param = ac->parameter();
141 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (ac->list ());
142 assert (al);
144 al->automation_state_changed.connect_same_thread (
145 _list_connections, boost::bind (&Automatable::automation_list_automation_state_changed, this, ac->parameter(), _1)
148 ControlSet::add_control (ac);
149 _can_automate_list.insert (param);
151 automation_list_automation_state_changed (param, al->automation_state ()); // sync everything up
154 string
155 Automatable::describe_parameter (Evoral::Parameter param)
157 /* derived classes like PluginInsert should override this */
159 if (param == Evoral::Parameter(GainAutomation)) {
160 return _("Fader");
161 } else if (param.type() == MidiCCAutomation) {
162 return string_compose("%1: %2 [%3]",
163 param.id(), midi_name(param.id()), int(param.channel()) + 1);
164 } else if (param.type() == MidiPgmChangeAutomation) {
165 return string_compose("Program [%1]", int(param.channel()) + 1);
166 } else if (param.type() == MidiPitchBenderAutomation) {
167 return string_compose("Bender [%1]", int(param.channel()) + 1);
168 } else if (param.type() == MidiChannelPressureAutomation) {
169 return string_compose("Pressure [%1]", int(param.channel()) + 1);
170 } else {
171 return EventTypeMap::instance().to_symbol(param);
175 void
176 Automatable::can_automate (Evoral::Parameter what)
178 _can_automate_list.insert (what);
181 /** \a legacy_param is used for loading legacy sessions where an object (IO, Panner)
182 * had a single automation parameter, with it's type implicit. Derived objects should
183 * pass that type and it will be used for the untyped AutomationList found.
186 Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter legacy_param)
188 Glib::Mutex::Lock lm (control_lock());
190 /* Don't clear controls, since some may be special derived Controllable classes */
192 XMLNodeList nlist = node.children();
193 XMLNodeIterator niter;
195 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
197 /*if (sscanf ((*niter)->name().c_str(), "parameter-%" PRIu32, &param) != 1) {
198 error << string_compose (_("%2: badly formatted node name in XML automation state, ignored"), _name) << endmsg;
199 continue;
202 if ((*niter)->name() == "AutomationList") {
204 const XMLProperty* id_prop = (*niter)->property("automation-id");
206 Evoral::Parameter param = (id_prop
207 ? EventTypeMap::instance().new_parameter(id_prop->value())
208 : legacy_param);
210 if (param.type() == NullAutomation) {
211 warning << "Automation has null type" << endl;
212 continue;
215 if (!id_prop) {
216 warning << "AutomationList node without automation-id property, "
217 << "using default: " << EventTypeMap::instance().to_symbol(legacy_param) << endmsg;
220 boost::shared_ptr<AutomationControl> existing = automation_control (param);
222 if (existing) {
223 existing->alist()->set_state (**niter, 3000);
224 } else {
225 boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
226 add_control (newcontrol);
227 boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
228 newcontrol->set_list(al);
231 } else {
232 error << "Expected AutomationList node, got '" << (*niter)->name() << "'" << endmsg;
236 _last_automation_snapshot = 0;
238 return 0;
241 XMLNode&
242 Automatable::get_automation_xml_state ()
244 Glib::Mutex::Lock lm (control_lock());
245 XMLNode* node = new XMLNode (Automatable::xml_node_name);
247 if (controls().empty()) {
248 return *node;
251 for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
252 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(li->second->list());
253 if (!l->empty()) {
254 node->add_child_nocopy (l->get_state ());
258 return *node;
261 void
262 Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState s)
264 Glib::Mutex::Lock lm (control_lock());
266 boost::shared_ptr<Evoral::Control> c = control (param, true);
267 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
269 if (s != l->automation_state()) {
270 l->set_automation_state (s);
271 _a_session.set_dirty ();
275 AutoState
276 Automatable::get_parameter_automation_state (Evoral::Parameter param)
278 AutoState result = Off;
280 boost::shared_ptr<Evoral::Control> c = control(param);
281 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
283 if (c) {
284 result = l->automation_state();
287 return result;
290 void
291 Automatable::set_parameter_automation_style (Evoral::Parameter param, AutoStyle s)
293 Glib::Mutex::Lock lm (control_lock());
295 boost::shared_ptr<Evoral::Control> c = control(param, true);
296 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
298 if (s != l->automation_style()) {
299 l->set_automation_style (s);
300 _a_session.set_dirty ();
304 AutoStyle
305 Automatable::get_parameter_automation_style (Evoral::Parameter param)
307 Glib::Mutex::Lock lm (control_lock());
309 boost::shared_ptr<Evoral::Control> c = control(param);
310 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
312 if (c) {
313 return l->automation_style();
314 } else {
315 return Absolute; // whatever
319 void
320 Automatable::protect_automation ()
322 typedef set<Evoral::Parameter> ParameterSet;
323 const ParameterSet& automated_params = what_can_be_automated ();
325 for (ParameterSet::const_iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
327 boost::shared_ptr<Evoral::Control> c = control(*i);
328 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
330 switch (l->automation_state()) {
331 case Write:
332 l->set_automation_state (Off);
333 break;
334 case Touch:
335 l->set_automation_state (Play);
336 break;
337 default:
338 break;
343 void
344 Automatable::automation_snapshot (framepos_t now, bool force)
346 if (force || _last_automation_snapshot > now || (now - _last_automation_snapshot) > _automation_interval) {
348 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
349 boost::shared_ptr<AutomationControl> c
350 = boost::dynamic_pointer_cast<AutomationControl>(i->second);
351 if (_a_session.transport_rolling() && c->automation_write()) {
352 c->list()->rt_add (now, i->second->user_double());
356 _last_automation_snapshot = now;
360 void
361 Automatable::transport_stopped (framepos_t now)
363 for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
365 boost::shared_ptr<AutomationControl> c
366 = boost::dynamic_pointer_cast<AutomationControl>(li->second);
367 if (c) {
368 boost::shared_ptr<AutomationList> l
369 = boost::dynamic_pointer_cast<AutomationList>(c->list());
371 if (l) {
372 /* Stop any active touch gesture just before we mark the write pass
373 as finished. If we don't do this, the transport can end up stopped with
374 an AutomationList thinking that a touch is still in progress and,
375 when the transport is re-started, a touch will magically
376 be happening without it ever have being started in the usual way.
378 l->stop_touch (true, now);
379 l->write_pass_finished (now);
381 if (l->automation_playback()) {
382 c->set_value(c->list()->eval(now));
385 if (l->automation_state() == Write) {
386 l->set_automation_state (Touch);
393 boost::shared_ptr<Evoral::Control>
394 Automatable::control_factory(const Evoral::Parameter& param)
396 boost::shared_ptr<AutomationList> list(new AutomationList(param));
397 Evoral::Control* control = NULL;
398 if (param.type() >= MidiCCAutomation && param.type() <= MidiChannelPressureAutomation) {
399 MidiTrack* mt = dynamic_cast<MidiTrack*>(this);
400 if (mt) {
401 control = new MidiTrack::MidiControl(mt, param);
402 } else {
403 warning << "MidiCCAutomation for non-MidiTrack" << endl;
405 } else if (param.type() == PluginAutomation) {
406 PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
407 if (pi) {
408 control = new PluginInsert::PluginControl(pi, param);
409 } else {
410 warning << "PluginAutomation for non-Plugin" << endl;
412 } else if (param.type() == GainAutomation) {
413 Amp* amp = dynamic_cast<Amp*>(this);
414 if (amp) {
415 control = new Amp::GainControl(X_("gaincontrol"), _a_session, amp, param);
416 } else {
417 warning << "GainAutomation for non-Amp" << endl;
419 } else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
420 Pannable* pannable = dynamic_cast<Pannable*>(this);
421 if (pannable) {
422 control = new PanControllable (_a_session, pannable->describe_parameter (param), pannable, param);
423 } else {
424 warning << "PanAutomation for non-Pannable" << endl;
428 if (!control) {
429 control = new AutomationControl(_a_session, param);
432 control->set_list(list);
433 return boost::shared_ptr<Evoral::Control>(control);
436 boost::shared_ptr<AutomationControl>
437 Automatable::automation_control (const Evoral::Parameter& id, bool create)
439 return boost::dynamic_pointer_cast<AutomationControl>(Evoral::ControlSet::control(id, create));
442 boost::shared_ptr<const AutomationControl>
443 Automatable::automation_control (const Evoral::Parameter& id) const
445 return boost::dynamic_pointer_cast<const AutomationControl>(Evoral::ControlSet::control(id));
448 void
449 Automatable::clear_controls ()
451 _control_connections.drop_connections ();
452 ControlSet::clear_controls ();
455 string
456 Automatable::value_as_string (boost::shared_ptr<AutomationControl> ac) const
458 std::stringstream s;
460 /* this is a the default fallback for this virtual method. Derived Automatables
461 are free to override this to display the values of their parameters/controls
462 in different ways.
465 // Hack to display CC as integer value, rather than double
466 if (ac->parameter().type() == MidiCCAutomation) {
467 s << lrint (ac->get_value());
468 } else {
469 s << std::fixed << std::setprecision(3) << ac->get_value();
472 return s.str ();