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