906ff4ed3ee2ad7b7a0a316a2293f3aaeab66d89
[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() == MuteAutomation) {
167                 return _("Mute");
168         } else if (param.type() == MidiCCAutomation) {
169                 return string_compose("Controller %1 [%2]", param.id(), int(param.channel()) + 1);
170         } else if (param.type() == MidiPgmChangeAutomation) {
171                 return string_compose("Program [%1]", int(param.channel()) + 1);
172         } else if (param.type() == MidiPitchBenderAutomation) {
173                 return string_compose("Bender [%1]", int(param.channel()) + 1);
174         } else if (param.type() == MidiChannelPressureAutomation) {
175                 return string_compose("Pressure [%1]", int(param.channel()) + 1);
176         } else if (param.type() == PluginPropertyAutomation) {
177                 return string_compose("Property %1", URIMap::instance().id_to_uri(param.id()));
178         } else {
179                 return EventTypeMap::instance().to_symbol(param);
180         }
181 }
182
183 void
184 Automatable::can_automate (Evoral::Parameter what)
185 {
186         _can_automate_list.insert (what);
187 }
188
189 /** \a legacy_param is used for loading legacy sessions where an object (IO, Panner)
190  * had a single automation parameter, with it's type implicit.  Derived objects should
191  * pass that type and it will be used for the untyped AutomationList found.
192  */
193 int
194 Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter legacy_param)
195 {
196         Glib::Threads::Mutex::Lock lm (control_lock());
197
198         /* Don't clear controls, since some may be special derived Controllable classes */
199
200         XMLNodeList nlist = node.children();
201         XMLNodeIterator niter;
202
203         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
204
205                 /*if (sscanf ((*niter)->name().c_str(), "parameter-%" PRIu32, &param) != 1) {
206                   error << string_compose (_("%2: badly formatted node name in XML automation state, ignored"), _name) << endmsg;
207                   continue;
208                   }*/
209
210                 if ((*niter)->name() == "AutomationList") {
211
212                         const XMLProperty* id_prop = (*niter)->property("automation-id");
213
214                         Evoral::Parameter param = (id_prop
215                                         ? EventTypeMap::instance().new_parameter(id_prop->value())
216                                         : legacy_param);
217
218                         if (param.type() == NullAutomation) {
219                                 warning << "Automation has null type" << endl;
220                                 continue;
221                         }
222
223                         if (!id_prop) {
224                                 warning << "AutomationList node without automation-id property, "
225                                         << "using default: " << EventTypeMap::instance().to_symbol(legacy_param) << endmsg;
226                         }
227
228                         boost::shared_ptr<AutomationControl> existing = automation_control (param);
229
230                         if (existing) {
231                                 existing->alist()->set_state (**niter, 3000);
232                         } else {
233                                 boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
234                                 add_control (newcontrol);
235                                 boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
236                                 newcontrol->set_list(al);
237                         }
238
239                 } else {
240                         error << "Expected AutomationList node, got '" << (*niter)->name() << "'" << endmsg;
241                 }
242         }
243
244         return 0;
245 }
246
247 XMLNode&
248 Automatable::get_automation_xml_state ()
249 {
250         Glib::Threads::Mutex::Lock lm (control_lock());
251         XMLNode* node = new XMLNode (Automatable::xml_node_name);
252
253         if (controls().empty()) {
254                 return *node;
255         }
256
257         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
258                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(li->second->list());
259                 if (l && !l->empty()) {
260                         node->add_child_nocopy (l->get_state ());
261                 }
262         }
263
264         return *node;
265 }
266
267 void
268 Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState s)
269 {
270         Glib::Threads::Mutex::Lock lm (control_lock());
271
272         boost::shared_ptr<AutomationControl> c = automation_control (param, true);
273
274         if (c && (s != c->automation_state())) {
275                 c->set_automation_state (s);
276                 _a_session.set_dirty ();
277         }
278 }
279
280 AutoState
281 Automatable::get_parameter_automation_state (Evoral::Parameter param)
282 {
283         AutoState result = Off;
284
285         boost::shared_ptr<AutomationControl> c = automation_control(param);
286         
287         if (c) {
288                 result = c->automation_state();
289         }
290
291         return result;
292 }
293
294 void
295 Automatable::set_parameter_automation_style (Evoral::Parameter param, AutoStyle s)
296 {
297         Glib::Threads::Mutex::Lock lm (control_lock());
298
299         boost::shared_ptr<AutomationControl> c = automation_control(param, true);
300
301         if (c && (s != c->automation_style())) {
302                 c->set_automation_style (s);
303                 _a_session.set_dirty ();
304         }
305 }
306
307 AutoStyle
308 Automatable::get_parameter_automation_style (Evoral::Parameter param)
309 {
310         Glib::Threads::Mutex::Lock lm (control_lock());
311
312         boost::shared_ptr<Evoral::Control> c = control(param);
313         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
314
315         if (c) {
316                 return l->automation_style();
317         } else {
318                 return Absolute; // whatever
319         }
320 }
321
322 void
323 Automatable::protect_automation ()
324 {
325         typedef set<Evoral::Parameter> ParameterSet;
326         const ParameterSet& automated_params = what_can_be_automated ();
327
328         for (ParameterSet::const_iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
329
330                 boost::shared_ptr<Evoral::Control> c = control(*i);
331                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
332
333                 switch (l->automation_state()) {
334                 case Write:
335                         l->set_automation_state (Off);
336                         break;
337                 case Touch:
338                         l->set_automation_state (Play);
339                         break;
340                 default:
341                         break;
342                 }
343         }
344 }
345
346 void
347 Automatable::transport_located (framepos_t now)
348 {
349         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
350
351                 boost::shared_ptr<AutomationControl> c
352                                 = boost::dynamic_pointer_cast<AutomationControl>(li->second);
353                 if (c) {
354                         boost::shared_ptr<AutomationList> l
355                                 = boost::dynamic_pointer_cast<AutomationList>(c->list());
356
357                         if (l) {
358                                 l->start_write_pass (now);
359                         }
360                 }
361         }
362 }
363
364 void
365 Automatable::transport_stopped (framepos_t now)
366 {
367         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
368                 boost::shared_ptr<AutomationControl> c =
369                         boost::dynamic_pointer_cast<AutomationControl>(li->second);
370                 if (!c) {
371                         continue;
372                 }
373
374                 boost::shared_ptr<AutomationList> l =
375                         boost::dynamic_pointer_cast<AutomationList>(c->list());
376                 if (!l) {
377                         continue;
378                 }
379
380                 /* Stop any active touch gesture just before we mark the write pass
381                    as finished.  If we don't do this, the transport can end up stopped with
382                    an AutomationList thinking that a touch is still in progress and,
383                    when the transport is re-started, a touch will magically
384                    be happening without it ever have being started in the usual way.
385                 */
386                 l->stop_touch (true, now);
387                 l->write_pass_finished (now, Config->get_automation_thinning_factor());
388
389                 if (l->automation_playback()) {
390                         c->set_value(c->list()->eval(now));
391                 }
392
393                 if (l->automation_state() == Write) {
394                         l->set_automation_state (Touch);
395                 }
396         }
397 }
398
399 boost::shared_ptr<Evoral::Control>
400 Automatable::control_factory(const Evoral::Parameter& param)
401 {
402         boost::shared_ptr<AutomationList> list(new AutomationList(param));
403         Evoral::Control* control = NULL;
404         ParameterDescriptor desc(param);
405         if (param.type() >= MidiCCAutomation && param.type() <= MidiChannelPressureAutomation) {
406                 MidiTrack* mt = dynamic_cast<MidiTrack*>(this);
407                 if (mt) {
408                         control = new MidiTrack::MidiControl(mt, param);
409                         list.reset();  // No list, this is region "automation"
410                 } else {
411                         warning << "MidiCCAutomation for non-MidiTrack" << endl;
412                 }
413         } else if (param.type() == PluginAutomation) {
414                 PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
415                 if (pi) {
416                         pi->plugin(0)->get_parameter_descriptor(param.id(), desc);
417                         control = new PluginInsert::PluginControl(pi, param, desc);
418                 } else {
419                         warning << "PluginAutomation for non-Plugin" << endl;
420                 }
421         } else if (param.type() == PluginPropertyAutomation) {
422                 PluginInsert* pi = dynamic_cast<PluginInsert*>(this);
423                 if (pi) {
424                         desc = pi->plugin(0)->get_property_descriptor(param.id());
425                         if (desc.datatype != Variant::NOTHING) {
426                                 if (!Variant::type_is_numeric(desc.datatype)) {
427                                         list.reset();  // Can't automate non-numeric data yet
428                                 }
429                                 control = new PluginInsert::PluginPropertyControl(pi, param, desc, list);
430                         }
431                 } else {
432                         warning << "PluginPropertyAutomation for non-Plugin" << endl;
433                 }
434         } else if (param.type() == GainAutomation) {
435                 Amp* amp = dynamic_cast<Amp*>(this);
436                 if (amp) {
437                         control = new Amp::GainControl(X_("gaincontrol"), _a_session, amp, param);
438                 } else {
439                         warning << "GainAutomation for non-Amp" << endl;
440                 }
441         } else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
442                 Pannable* pannable = dynamic_cast<Pannable*>(this);
443                 if (pannable) {
444                         control = new PanControllable (_a_session, pannable->describe_parameter (param), pannable, param);
445                 } else {
446                         warning << "PanAutomation for non-Pannable" << endl;
447                 }
448         }
449
450         if (!control) {
451                 control = new AutomationControl(_a_session, param, desc);
452         }
453
454         control->set_list(list);
455         return boost::shared_ptr<Evoral::Control>(control);
456 }
457
458 boost::shared_ptr<AutomationControl>
459 Automatable::automation_control (const Evoral::Parameter& id, bool create)
460 {
461         return boost::dynamic_pointer_cast<AutomationControl>(Evoral::ControlSet::control(id, create));
462 }
463
464 boost::shared_ptr<const AutomationControl>
465 Automatable::automation_control (const Evoral::Parameter& id) const
466 {
467         return boost::dynamic_pointer_cast<const AutomationControl>(Evoral::ControlSet::control(id));
468 }
469
470 void
471 Automatable::clear_controls ()
472 {
473         _control_connections.drop_connections ();
474         ControlSet::clear_controls ();
475 }
476
477 string
478 Automatable::value_as_string (boost::shared_ptr<AutomationControl> ac) const
479 {
480         return ARDOUR::value_as_string(ac->desc(), ac->get_value());
481 }