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