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