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