NOOP, remove trailing tabs/whitespace.
[ardour.git] / libs / ardour / automatable.cc
1 /*
2     Copyright (C) 2001,2007 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <fstream>
21 #include <cstdio>
22 #include <errno.h>
23
24 #include <glibmm/miscutils.h>
25
26 #include "pbd/error.h"
27
28 #include "ardour/amp.h"
29 #include "ardour/automatable.h"
30 #include "ardour/event_type_map.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/pan_controllable.h"
33 #include "ardour/pannable.h"
34 #include "ardour/plugin.h"
35 #include "ardour/plugin_insert.h"
36 #include "ardour/session.h"
37 #include "ardour/uri_map.h"
38 #include "ardour/value_as_string.h"
39
40 #include "i18n.h"
41
42 using namespace std;
43 using namespace ARDOUR;
44 using namespace PBD;
45
46 const string Automatable::xml_node_name = X_("Automation");
47
48 Automatable::Automatable(Session& session)
49         : _a_session(session)
50 {
51 }
52
53 Automatable::Automatable (const Automatable& other)
54         : ControlSet (other)
55         , _a_session (other._a_session)
56 {
57         Glib::Threads::Mutex::Lock lm (other._control_lock);
58
59         for (Controls::const_iterator i = other._controls.begin(); i != other._controls.end(); ++i) {
60                 boost::shared_ptr<Evoral::Control> ac (control_factory (i->first));
61                 add_control (ac);
62         }
63 }
64
65 Automatable::~Automatable ()
66 {
67         {
68                 Glib::Threads::Mutex::Lock lm (_control_lock);
69
70                 for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
71                         boost::dynamic_pointer_cast<AutomationControl>(li->second)->drop_references ();
72                 }
73         }
74 }
75
76 int
77 Automatable::old_set_automation_state (const XMLNode& node)
78 {
79         const XMLProperty *prop;
80
81         if ((prop = node.property ("path")) != 0) {
82                 load_automation (prop->value());
83         } else {
84                 warning << _("Automation node has no path property") << endmsg;
85         }
86
87         return 0;
88 }
89
90 int
91 Automatable::load_automation (const string& path)
92 {
93         string fullpath;
94
95         if (Glib::path_is_absolute (path)) { // legacy
96                 fullpath = path;
97         } else {
98                 fullpath = _a_session.automation_dir();
99                 fullpath += path;
100         }
101         ifstream in (fullpath.c_str());
102
103         if (!in) {
104                 warning << string_compose(_("cannot open %2 to load automation data (%3)")
105                                 , fullpath, strerror (errno)) << endmsg;
106                 return 1;
107         }
108
109         Glib::Threads::Mutex::Lock lm (control_lock());
110         set<Evoral::Parameter> tosave;
111         controls().clear ();
112
113         while (in) {
114                 double when;
115                 double value;
116                 uint32_t port;
117
118                 in >> port;  if (!in) break;
119                 in >> when;  if (!in) goto bad;
120                 in >> value; if (!in) goto bad;
121
122                 Evoral::Parameter param(PluginAutomation, 0, port);
123                 /* FIXME: this is legacy and only used for plugin inserts?  I think? */
124                 boost::shared_ptr<Evoral::Control> c = control (param, true);
125                 c->list()->add (when, value);
126                 tosave.insert (param);
127         }
128
129         return 0;
130
131   bad:
132         error << string_compose(_("cannot load automation data from %2"), fullpath) << endmsg;
133         controls().clear ();
134         return -1;
135 }
136
137 void
138 Automatable::add_control(boost::shared_ptr<Evoral::Control> ac)
139 {
140         Evoral::Parameter param = ac->parameter();
141
142         boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (ac->list ());
143
144         if (al) {
145                 al->automation_state_changed.connect_same_thread (
146                         _list_connections,
147                         boost::bind (&Automatable::automation_list_automation_state_changed,
148                                      this, ac->parameter(), _1));
149         }
150
151         ControlSet::add_control (ac);
152
153         if (al) {
154                 _can_automate_list.insert (param);
155                 automation_list_automation_state_changed (param, al->automation_state ()); // sync everything up
156         }
157 }
158
159 string
160 Automatable::describe_parameter (Evoral::Parameter param)
161 {
162         /* derived classes like PluginInsert should override this */
163
164         if (param == Evoral::Parameter(GainAutomation)) {
165                 return _("Fader");
166         } else if (param.type() == TrimAutomation) {
167                 return _("Trim");
168         } else if (param.type() == MuteAutomation) {
169                 return _("Mute");
170         } else if (param.type() == MidiCCAutomation) {
171                 return string_compose("Controller %1 [%2]", param.id(), int(param.channel()) + 1);
172         } else if (param.type() == MidiPgmChangeAutomation) {
173                 return string_compose("Program [%1]", int(param.channel()) + 1);
174         } else if (param.type() == MidiPitchBenderAutomation) {
175                 return string_compose("Bender [%1]", int(param.channel()) + 1);
176         } else if (param.type() == MidiChannelPressureAutomation) {
177                 return string_compose("Pressure [%1]", int(param.channel()) + 1);
178 #ifdef LV2_SUPPORT
179         } else if (param.type() == PluginPropertyAutomation) {
180                 return string_compose("Property %1", URIMap::instance().id_to_uri(param.id()));
181 #endif
182         } else {
183                 return EventTypeMap::instance().to_symbol(param);
184         }
185 }
186
187 void
188 Automatable::can_automate (Evoral::Parameter what)
189 {
190         _can_automate_list.insert (what);
191 }
192
193 /** \a legacy_param is used for loading legacy sessions where an object (IO, Panner)
194  * had a single automation parameter, with it's type implicit.  Derived objects should
195  * pass that type and it will be used for the untyped AutomationList found.
196  */
197 int
198 Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter legacy_param)
199 {
200         Glib::Threads::Mutex::Lock lm (control_lock());
201
202         /* Don't clear controls, since some may be special derived Controllable classes */
203
204         XMLNodeList nlist = node.children();
205         XMLNodeIterator niter;
206
207         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
208
209                 /*if (sscanf ((*niter)->name().c_str(), "parameter-%" PRIu32, &param) != 1) {
210                   error << string_compose (_("%2: badly formatted node name in XML automation state, ignored"), _name) << endmsg;
211                   continue;
212                   }*/
213
214                 if ((*niter)->name() == "AutomationList") {
215
216                         const XMLProperty* id_prop = (*niter)->property("automation-id");
217
218                         Evoral::Parameter param = (id_prop
219                                         ? EventTypeMap::instance().from_symbol(id_prop->value())
220                                         : legacy_param);
221
222                         if (param.type() == NullAutomation) {
223                                 warning << "Automation has null type" << endl;
224                                 continue;
225                         }
226
227                         if (!id_prop) {
228                                 warning << "AutomationList node without automation-id property, "
229                                         << "using default: " << EventTypeMap::instance().to_symbol(legacy_param) << endmsg;
230                         }
231
232                         boost::shared_ptr<AutomationControl> existing = automation_control (param);
233
234                         if (existing) {
235                                 existing->alist()->set_state (**niter, 3000);
236                         } else {
237                                 boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
238                                 add_control (newcontrol);
239                                 boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
240                                 newcontrol->set_list(al);
241                         }
242
243                 } else {
244                         error << "Expected AutomationList node, got '" << (*niter)->name() << "'" << endmsg;
245                 }
246         }
247
248         return 0;
249 }
250
251 XMLNode&
252 Automatable::get_automation_xml_state ()
253 {
254         Glib::Threads::Mutex::Lock lm (control_lock());
255         XMLNode* node = new XMLNode (Automatable::xml_node_name);
256
257         if (controls().empty()) {
258                 return *node;
259         }
260
261         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
262                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(li->second->list());
263                 if (l && !l->empty()) {
264                         node->add_child_nocopy (l->get_state ());
265                 }
266         }
267
268         return *node;
269 }
270
271 void
272 Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState s)
273 {
274         Glib::Threads::Mutex::Lock lm (control_lock());
275
276         boost::shared_ptr<AutomationControl> c = automation_control (param, true);
277
278         if (c && (s != c->automation_state())) {
279                 c->set_automation_state (s);
280                 _a_session.set_dirty ();
281                 AutomationStateChanged(); /* Emit signal */
282         }
283 }
284
285 AutoState
286 Automatable::get_parameter_automation_state (Evoral::Parameter param)
287 {
288         AutoState result = Off;
289
290         boost::shared_ptr<AutomationControl> c = automation_control(param);
291
292         if (c) {
293                 result = c->automation_state();
294         }
295
296         return result;
297 }
298
299 void
300 Automatable::set_parameter_automation_style (Evoral::Parameter param, AutoStyle s)
301 {
302         Glib::Threads::Mutex::Lock lm (control_lock());
303
304         boost::shared_ptr<AutomationControl> c = automation_control(param, true);
305
306         if (c && (s != c->automation_style())) {
307                 c->set_automation_style (s);
308                 _a_session.set_dirty ();
309         }
310 }
311
312 AutoStyle
313 Automatable::get_parameter_automation_style (Evoral::Parameter param)
314 {
315         Glib::Threads::Mutex::Lock lm (control_lock());
316
317         boost::shared_ptr<Evoral::Control> c = control(param);
318         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
319
320         if (c) {
321                 return l->automation_style();
322         } else {
323                 return Absolute; // whatever
324         }
325 }
326
327 void
328 Automatable::protect_automation ()
329 {
330         typedef set<Evoral::Parameter> ParameterSet;
331         const ParameterSet& automated_params = what_can_be_automated ();
332
333         for (ParameterSet::const_iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
334
335                 boost::shared_ptr<Evoral::Control> c = control(*i);
336                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
337
338                 switch (l->automation_state()) {
339                 case Write:
340                         l->set_automation_state (Off);
341                         break;
342                 case Touch:
343                         l->set_automation_state (Play);
344                         break;
345                 default:
346                         break;
347                 }
348         }
349 }
350
351 void
352 Automatable::transport_located (framepos_t now)
353 {
354         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
355
356                 boost::shared_ptr<AutomationControl> c
357                                 = boost::dynamic_pointer_cast<AutomationControl>(li->second);
358                 if (c) {
359                         boost::shared_ptr<AutomationList> l
360                                 = boost::dynamic_pointer_cast<AutomationList>(c->list());
361
362                         if (l) {
363                                 l->start_write_pass (now);
364                         }
365                 }
366         }
367 }
368
369 void
370 Automatable::transport_stopped (framepos_t now)
371 {
372         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
373                 boost::shared_ptr<AutomationControl> c =
374                         boost::dynamic_pointer_cast<AutomationControl>(li->second);
375                 if (!c) {
376                         continue;
377                 }
378
379                 boost::shared_ptr<AutomationList> l =
380                         boost::dynamic_pointer_cast<AutomationList>(c->list());
381                 if (!l) {
382                         continue;
383                 }
384
385                 /* Stop any active touch gesture just before we mark the write pass
386                    as finished.  If we don't do this, the transport can end up stopped with
387                    an AutomationList thinking that a touch is still in progress and,
388                    when the transport is re-started, a touch will magically
389                    be happening without it ever have being started in the usual way.
390                 */
391                 l->stop_touch (true, now);
392                 l->write_pass_finished (now, Config->get_automation_thinning_factor());
393
394                 if (l->automation_playback()) {
395                         c->set_value(c->list()->eval(now));
396                 }
397
398                 if (l->automation_state() == Write) {
399                         l->set_automation_state (Touch);
400                 }
401         }
402 }
403
404 boost::shared_ptr<Evoral::Control>
405 Automatable::control_factory(const Evoral::Parameter& param)
406 {
407         Evoral::Control*                  control   = NULL;
408         bool                              make_list = true;
409         ParameterDescriptor               desc(param);
410         boost::shared_ptr<AutomationList> list;
411         if (param.type() >= MidiCCAutomation && param.type() <= MidiChannelPressureAutomation) {
412                 MidiTrack* mt = dynamic_cast<MidiTrack*>(this);
413                 if (mt) {
414                         control = new MidiTrack::MidiControl(mt, param);
415                         make_list = false;  // No list, this is region "automation"
416                 }
417         } else if (param.type() == PluginAutomation) {
418                 PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
419                 if (pi) {
420                         pi->plugin(0)->get_parameter_descriptor(param.id(), desc);
421                         control = new PluginInsert::PluginControl(pi, param, desc);
422                 } else {
423                         warning << "PluginAutomation for non-Plugin" << endl;
424                 }
425         } else if (param.type() == PluginPropertyAutomation) {
426                 PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
427                 if (pi) {
428                         desc = pi->plugin(0)->get_property_descriptor(param.id());
429                         if (desc.datatype != Variant::NOTHING) {
430                                 if (!Variant::type_is_numeric(desc.datatype)) {
431                                         make_list = false;  // Can't automate non-numeric data yet
432                                 } else {
433                                         list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
434                                 }
435                                 control = new PluginInsert::PluginPropertyControl(pi, param, desc, list);
436                         }
437                 } else {
438                         warning << "PluginPropertyAutomation for non-Plugin" << endl;
439                 }
440         } else if (param.type() == GainAutomation) {
441                 Amp* amp = dynamic_cast<Amp*>(this);
442                 if (amp) {
443                         control = new Amp::GainControl(X_("gaincontrol"), _a_session, amp, param);
444                 } else {
445                         warning << "GainAutomation for non-Amp" << endl;
446                 }
447         } else if (param.type() == TrimAutomation) {
448                 Amp* amp = dynamic_cast<Amp*>(this);
449                 if (amp) {
450                         control = new Amp::GainControl(X_("trimcontrol"), _a_session, amp, param);
451                 } else {
452                         warning << "TrimAutomation for non-Amp" << endl;
453                 }
454         } else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
455                 Pannable* pannable = dynamic_cast<Pannable*>(this);
456                 if (pannable) {
457                         control = new PanControllable (_a_session, pannable->describe_parameter (param), pannable, param);
458                 } else {
459                         warning << "PanAutomation for non-Pannable" << endl;
460                 }
461         }
462
463         if (make_list && !list) {
464                 list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
465         }
466
467         if (!control) {
468                 control = new AutomationControl(_a_session, param, desc, list);
469         }
470
471         return boost::shared_ptr<Evoral::Control>(control);
472 }
473
474 boost::shared_ptr<AutomationControl>
475 Automatable::automation_control (const Evoral::Parameter& id, bool create)
476 {
477         return boost::dynamic_pointer_cast<AutomationControl>(Evoral::ControlSet::control(id, create));
478 }
479
480 boost::shared_ptr<const AutomationControl>
481 Automatable::automation_control (const Evoral::Parameter& id) const
482 {
483         return boost::dynamic_pointer_cast<const AutomationControl>(Evoral::ControlSet::control(id));
484 }
485
486 void
487 Automatable::clear_controls ()
488 {
489         _control_connections.drop_connections ();
490         ControlSet::clear_controls ();
491 }
492
493 string
494 Automatable::value_as_string (boost::shared_ptr<AutomationControl> ac) const
495 {
496         return ARDOUR::value_as_string(ac->desc(), ac->get_value());
497 }