major, substantive reworking of how we store GUI information (visibility, height...
[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         _last_automation_snapshot = 0;
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::Mutex::Lock lm (control_lock());
107         set<Evoral::Parameter> tosave;
108         controls().clear ();
109
110         _last_automation_snapshot = 0;
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::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         _last_automation_snapshot = 0;
237
238         return 0;
239 }
240
241 XMLNode&
242 Automatable::get_automation_xml_state ()
243 {
244         Glib::Mutex::Lock lm (control_lock());
245         XMLNode* node = new XMLNode (Automatable::xml_node_name);
246
247         if (controls().empty()) {
248                 return *node;
249         }
250
251         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
252                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(li->second->list());
253                 if (!l->empty()) {
254                         node->add_child_nocopy (l->get_state ());
255                 }
256         }
257
258         return *node;
259 }
260
261 void
262 Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState s)
263 {
264         Glib::Mutex::Lock lm (control_lock());
265
266         boost::shared_ptr<Evoral::Control> c = control (param, true);
267         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
268
269         if (s != l->automation_state()) {
270                 l->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<Evoral::Control> c = control(param);
281         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
282
283         if (c) {
284                 result = l->automation_state();
285         }
286
287         return result;
288 }
289
290 void
291 Automatable::set_parameter_automation_style (Evoral::Parameter param, AutoStyle s)
292 {
293         Glib::Mutex::Lock lm (control_lock());
294
295         boost::shared_ptr<Evoral::Control> c = control(param, true);
296         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
297
298         if (s != l->automation_style()) {
299                 l->set_automation_style (s);
300                 _a_session.set_dirty ();
301         }
302 }
303
304 AutoStyle
305 Automatable::get_parameter_automation_style (Evoral::Parameter param)
306 {
307         Glib::Mutex::Lock lm (control_lock());
308
309         boost::shared_ptr<Evoral::Control> c = control(param);
310         boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
311
312         if (c) {
313                 return l->automation_style();
314         } else {
315                 return Absolute; // whatever
316         }
317 }
318
319 void
320 Automatable::protect_automation ()
321 {
322         typedef set<Evoral::Parameter> ParameterSet;
323         const ParameterSet& automated_params = what_can_be_automated ();
324
325         for (ParameterSet::const_iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
326
327                 boost::shared_ptr<Evoral::Control> c = control(*i);
328                 boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
329
330                 switch (l->automation_state()) {
331                 case Write:
332                         l->set_automation_state (Off);
333                         break;
334                 case Touch:
335                         l->set_automation_state (Play);
336                         break;
337                 default:
338                         break;
339                 }
340         }
341 }
342
343 void
344 Automatable::automation_snapshot (framepos_t now, bool force)
345 {
346         if (force || _last_automation_snapshot > now || (now - _last_automation_snapshot) > _automation_interval) {
347
348                 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
349                         boost::shared_ptr<AutomationControl> c
350                                         = boost::dynamic_pointer_cast<AutomationControl>(i->second);
351                         if (_a_session.transport_rolling() && c->automation_write()) {
352                                 c->list()->rt_add (now, i->second->user_double());
353                         }
354                 }
355
356                 _last_automation_snapshot = now;
357         }
358 }
359
360 void
361 Automatable::transport_stopped (framepos_t now)
362 {
363         for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
364
365                 boost::shared_ptr<AutomationControl> c
366                                 = boost::dynamic_pointer_cast<AutomationControl>(li->second);
367                 if (c) {
368                         boost::shared_ptr<AutomationList> l
369                                 = boost::dynamic_pointer_cast<AutomationList>(c->list());
370
371                         if (l) {
372                                 /* Stop any active touch gesture just before we mark the write pass
373                                    as finished.  If we don't do this, the transport can end up stopped with
374                                    an AutomationList thinking that a touch is still in progress and,
375                                    when the transport is re-started, a touch will magically
376                                    be happening without it ever have being started in the usual way.
377                                 */
378                                 l->stop_touch (true, now);
379                                 l->write_pass_finished (now);
380
381                                 if (l->automation_playback()) {
382                                         c->set_value(c->list()->eval(now));
383                                 }
384
385                                 if (l->automation_state() == Write) {
386                                         l->set_automation_state (Touch);
387                                 }
388                         }
389                 }
390         }
391 }
392
393 boost::shared_ptr<Evoral::Control>
394 Automatable::control_factory(const Evoral::Parameter& param)
395 {
396         boost::shared_ptr<AutomationList> list(new AutomationList(param));
397         Evoral::Control* control = NULL;
398         if (param.type() >= MidiCCAutomation && param.type() <= MidiChannelPressureAutomation) {
399                 MidiTrack* mt = dynamic_cast<MidiTrack*>(this);
400                 if (mt) {
401                         control = new MidiTrack::MidiControl(mt, param);
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 }