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