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