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