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