Make automation record undo per pass rather than per touch.
[ardour.git] / libs / ardour / automation_list.cc
1 /*
2     Copyright (C) 2002 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 <set>
21 #include <climits>
22 #include <float.h>
23 #include <cmath>
24 #include <sstream>
25 #include <algorithm>
26 #include "ardour/automation_list.h"
27 #include "ardour/event_type_map.h"
28 #include "ardour/parameter_descriptor.h"
29 #include "evoral/Curve.hpp"
30 #include "pbd/stacktrace.h"
31 #include "pbd/enumwriter.h"
32
33 #include "i18n.h"
34
35 using namespace std;
36 using namespace ARDOUR;
37 using namespace PBD;
38
39 PBD::Signal1<void,AutomationList *> AutomationList::AutomationListCreated;
40
41 #if 0
42 static void dumpit (const AutomationList& al, string prefix = "")
43 {
44         cerr << prefix << &al << endl;
45         for (AutomationList::const_iterator i = al.begin(); i != al.end(); ++i) {
46                 cerr << prefix << '\t' << (*i)->when << ',' << (*i)->value << endl;
47         }
48         cerr << "\n";
49 }
50 #endif
51 AutomationList::AutomationList (const Evoral::Parameter& id, const Evoral::ParameterDescriptor& desc)
52         : ControlList(id, desc)
53         , _before (0)
54 {
55         _state = Off;
56         _style = Absolute;
57         g_atomic_int_set (&_touching, 0);
58
59         create_curve_if_necessary();
60
61         assert(_parameter.type() != NullAutomation);
62         AutomationListCreated(this);
63 }
64
65 AutomationList::AutomationList (const Evoral::Parameter& id)
66         : ControlList(id, ARDOUR::ParameterDescriptor(id))
67         , _before (0)
68 {
69         _state = Off;
70         _style = Absolute;
71         g_atomic_int_set (&_touching, 0);
72
73         create_curve_if_necessary();
74
75         assert(_parameter.type() != NullAutomation);
76         AutomationListCreated(this);
77 }
78
79 AutomationList::AutomationList (const AutomationList& other)
80         : StatefulDestructible()
81         , ControlList(other)
82         , _before (0)
83 {
84         _style = other._style;
85         _state = other._state;
86         g_atomic_int_set (&_touching, other.touching());
87
88         create_curve_if_necessary();
89
90         assert(_parameter.type() != NullAutomation);
91         AutomationListCreated(this);
92 }
93
94 AutomationList::AutomationList (const AutomationList& other, double start, double end)
95         : ControlList(other, start, end)
96         , _before (0)
97 {
98         _style = other._style;
99         _state = other._state;
100         g_atomic_int_set (&_touching, other.touching());
101
102         create_curve_if_necessary();
103
104         assert(_parameter.type() != NullAutomation);
105         AutomationListCreated(this);
106 }
107
108 /** @param id is used for legacy sessions where the type is not present
109  * in or below the AutomationList node.  It is used if @param id is non-null.
110  */
111 AutomationList::AutomationList (const XMLNode& node, Evoral::Parameter id)
112         : ControlList(id, ARDOUR::ParameterDescriptor(id))
113         , _before (0)
114 {
115         g_atomic_int_set (&_touching, 0);
116         _state = Off;
117         _style = Absolute;
118
119         set_state (node, Stateful::loading_state_version);
120
121         if (id) {
122                 _parameter = id;
123         }
124
125         create_curve_if_necessary();
126
127         assert(_parameter.type() != NullAutomation);
128         AutomationListCreated(this);
129 }
130
131 AutomationList::~AutomationList()
132 {
133 }
134
135 boost::shared_ptr<Evoral::ControlList>
136 AutomationList::create(const Evoral::Parameter&           id,
137                        const Evoral::ParameterDescriptor& desc)
138 {
139         return boost::shared_ptr<Evoral::ControlList>(new AutomationList(id, desc));
140 }
141
142 void
143 AutomationList::create_curve_if_necessary()
144 {
145         switch (_parameter.type()) {
146         case GainAutomation:
147         case TrimAutomation:
148         case PanAzimuthAutomation:
149         case PanElevationAutomation:
150         case PanWidthAutomation:
151         case FadeInAutomation:
152         case FadeOutAutomation:
153         case EnvelopeAutomation:
154                 create_curve();
155                 break;
156         default:
157                 break;
158         }
159 }
160
161 AutomationList&
162 AutomationList::operator= (const AutomationList& other)
163 {
164         if (this != &other) {
165
166
167                 ControlList::operator= (other);
168                 _state = other._state;
169                 _style = other._style;
170                 _touching = other._touching;
171
172                 mark_dirty ();
173                 maybe_signal_changed ();
174         }
175
176         return *this;
177 }
178
179 void
180 AutomationList::maybe_signal_changed ()
181 {
182         ControlList::maybe_signal_changed ();
183
184         if (!ControlList::frozen()) {
185                 StateChanged (); /* EMIT SIGNAL */
186         }
187 }
188
189 void
190 AutomationList::set_automation_state (AutoState s)
191 {
192         if (s != _state) {
193                 _state = s;
194                 if (s == Write) {
195                         _before = &get_state ();
196                 }
197                 automation_state_changed (s); /* EMIT SIGNAL */
198         }
199 }
200
201 void
202 AutomationList::set_automation_style (AutoStyle s)
203 {
204         if (s != _style) {
205                 _style = s;
206                 automation_style_changed (); /* EMIT SIGNAL */
207         }
208 }
209
210 void
211 AutomationList::start_write_pass (double when)
212 {
213         if (in_new_write_pass ()) {
214                 _before = &get_state ();
215         }
216         ControlList::start_write_pass (when);
217 }
218
219 void
220 AutomationList::write_pass_finished (double when, double thinning_factor)
221 {
222         ControlList::write_pass_finished (when, thinning_factor);
223         _before = 0;
224 }
225
226 void
227 AutomationList::start_touch (double when)
228 {
229         if (_state == Touch) {
230                 start_write_pass (when);
231         }
232
233         g_atomic_int_set (&_touching, 1);
234 }
235
236 void
237 AutomationList::stop_touch (bool mark, double)
238 {
239         if (g_atomic_int_get (&_touching) == 0) {
240                 /* this touch has already been stopped (probably by Automatable::transport_stopped),
241                    so we've nothing to do.
242                 */
243                 return;
244         }
245
246         g_atomic_int_set (&_touching, 0);
247
248         if (_state == Touch) {
249
250                 if (mark) {
251
252                         /* XXX need to mark the last added point with the
253                          * current time
254                          */
255                 }
256         }
257 }
258
259 void
260 AutomationList::thaw ()
261 {
262         ControlList::thaw();
263
264         if (_changed_when_thawed) {
265                 _changed_when_thawed = false;
266                 StateChanged(); /* EMIT SIGNAL */
267         }
268 }
269
270 XMLNode&
271 AutomationList::get_state ()
272 {
273         return state (true);
274 }
275
276 XMLNode&
277 AutomationList::state (bool full)
278 {
279         XMLNode* root = new XMLNode (X_("AutomationList"));
280         char buf[64];
281         LocaleGuard lg (X_("C"));
282
283         root->add_property ("automation-id", EventTypeMap::instance().to_symbol(_parameter));
284
285         root->add_property ("id", id().to_s());
286
287         snprintf (buf, sizeof (buf), "%.12g", _default_value);
288         root->add_property ("default", buf);
289         snprintf (buf, sizeof (buf), "%.12g", _min_yval);
290         root->add_property ("min-yval", buf);
291         snprintf (buf, sizeof (buf), "%.12g", _max_yval);
292         root->add_property ("max-yval", buf);
293
294         root->add_property ("interpolation-style", enum_2_string (_interpolation));
295
296         if (full) {
297                 /* never serialize state with Write enabled - too dangerous
298                    for the user's data
299                 */
300                 if (_state != Write) {
301                         root->add_property ("state", auto_state_to_string (_state));
302                 } else {
303                         if (_events.empty ()) {
304                                 root->add_property ("state", auto_state_to_string (Off));
305                         } else {
306                                 root->add_property ("state", auto_state_to_string (Touch));
307                         }
308                 }
309         } else {
310                 /* never save anything but Off for automation state to a template */
311                 root->add_property ("state", auto_state_to_string (Off));
312         }
313
314         root->add_property ("style", auto_style_to_string (_style));
315
316         if (!_events.empty()) {
317                 root->add_child_nocopy (serialize_events());
318         }
319
320         return *root;
321 }
322
323 XMLNode&
324 AutomationList::serialize_events ()
325 {
326         XMLNode* node = new XMLNode (X_("events"));
327         stringstream str;
328
329         str.precision(15);  //10 digits is enough digits for 24 hours at 96kHz
330
331         for (iterator xx = _events.begin(); xx != _events.end(); ++xx) {
332                 str << (double) (*xx)->when;
333                 str << ' ';
334                 str <<(double) (*xx)->value;
335                 str << '\n';
336         }
337
338         /* XML is a bit wierd */
339
340         XMLNode* content_node = new XMLNode (X_("foo")); /* it gets renamed by libxml when we set content */
341         content_node->set_content (str.str());
342
343         node->add_child_nocopy (*content_node);
344
345         return *node;
346 }
347
348 int
349 AutomationList::deserialize_events (const XMLNode& node)
350 {
351         if (node.children().empty()) {
352                 return -1;
353         }
354
355         XMLNode* content_node = node.children().front();
356
357         if (content_node->content().empty()) {
358                 return -1;
359         }
360
361         ControlList::freeze ();
362         clear ();
363
364         stringstream str (content_node->content());
365
366         double x;
367         double y;
368         bool ok = true;
369
370         while (str) {
371                 str >> x;
372                 if (!str) {
373                         break;
374                 }
375                 str >> y;
376                 if (!str) {
377                         ok = false;
378                         break;
379                 }
380                 fast_simple_add (x, y);
381         }
382
383         if (!ok) {
384                 clear ();
385                 error << _("automation list: cannot load coordinates from XML, all points ignored") << endmsg;
386         } else {
387                 mark_dirty ();
388                 maybe_signal_changed ();
389         }
390
391         thaw ();
392
393         return 0;
394 }
395
396 int
397 AutomationList::set_state (const XMLNode& node, int version)
398 {
399         LocaleGuard lg (X_("C"));
400         XMLNodeList nlist = node.children();
401         XMLNode* nsos;
402         XMLNodeIterator niter;
403         const XMLProperty* prop;
404
405         if (node.name() == X_("events")) {
406                 /* partial state setting*/
407                 return deserialize_events (node);
408         }
409
410         if (node.name() == X_("Envelope") || node.name() == X_("FadeOut") || node.name() == X_("FadeIn")) {
411
412                 if ((nsos = node.child (X_("AutomationList")))) {
413                         /* new school in old school clothing */
414                         return set_state (*nsos, version);
415                 }
416
417                 /* old school */
418
419                 const XMLNodeList& elist = node.children();
420                 XMLNodeConstIterator i;
421                 XMLProperty* prop;
422                 pframes_t x;
423                 double y;
424
425                 ControlList::freeze ();
426                 clear ();
427
428                 for (i = elist.begin(); i != elist.end(); ++i) {
429
430                         if ((prop = (*i)->property ("x")) == 0) {
431                                 error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
432                                 continue;
433                         }
434                         x = atoi (prop->value().c_str());
435
436                         if ((prop = (*i)->property ("y")) == 0) {
437                                 error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
438                                 continue;
439                         }
440                         y = atof (prop->value().c_str());
441
442                         fast_simple_add (x, y);
443                 }
444
445                 thaw ();
446
447                 return 0;
448         }
449
450         if (node.name() != X_("AutomationList") ) {
451                 error << string_compose (_("AutomationList: passed XML node called %1, not \"AutomationList\" - ignored"), node.name()) << endmsg;
452                 return -1;
453         }
454
455         if (set_id (node)) {
456                 /* update session AL list */
457                 AutomationListCreated(this);
458         }
459
460         if ((prop = node.property (X_("automation-id"))) != 0){
461                 _parameter = EventTypeMap::instance().from_symbol(prop->value());
462         } else {
463                 warning << "Legacy session: automation list has no automation-id property." << endmsg;
464         }
465
466         if ((prop = node.property (X_("interpolation-style"))) != 0) {
467                 _interpolation = (InterpolationStyle)string_2_enum(prop->value(), _interpolation);
468         } else {
469                 _interpolation = Linear;
470         }
471
472         if ((prop = node.property (X_("default"))) != 0){
473                 _default_value = atof (prop->value().c_str());
474         } else {
475                 _default_value = 0.0;
476         }
477
478         if ((prop = node.property (X_("style"))) != 0) {
479                 _style = string_to_auto_style (prop->value());
480         } else {
481                 _style = Absolute;
482         }
483
484         if ((prop = node.property (X_("state"))) != 0) {
485                 _state = string_to_auto_state (prop->value());
486                 if (_state == Write) {
487                         _state = Off;
488                 }
489                 automation_state_changed(_state);
490         } else {
491                 _state = Off;
492         }
493
494         if ((prop = node.property (X_("min-yval"))) != 0) {
495                 _min_yval = atof (prop->value ().c_str());
496         } else {
497                 _min_yval = FLT_MIN;
498         }
499
500         if ((prop = node.property (X_("max-yval"))) != 0) {
501                 _max_yval = atof (prop->value ().c_str());
502         } else {
503                 _max_yval = FLT_MAX;
504         }
505
506         bool have_events = false;
507
508         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
509                 if ((*niter)->name() == X_("events")) {
510                         deserialize_events (*(*niter));
511                         have_events = true;
512                 }
513         }
514
515         if (!have_events) {
516                 /* there was no Events child node; clear any current events */
517                 freeze ();
518                 clear ();
519                 mark_dirty ();
520                 maybe_signal_changed ();
521                 thaw ();
522         }
523
524         return 0;
525 }
526
527 bool
528 AutomationList::operator!= (AutomationList const & other) const
529 {
530         return (
531                 static_cast<ControlList const &> (*this) != static_cast<ControlList const &> (other) ||
532                 _state != other._state ||
533                 _style != other._style ||
534                 _touching != other._touching
535                 );
536 }
537
538 PBD::PropertyBase *
539 AutomationListProperty::clone () const
540 {
541         return new AutomationListProperty (
542                 this->property_id(),
543                 boost::shared_ptr<AutomationList> (new AutomationList (*this->_old.get())),
544                 boost::shared_ptr<AutomationList> (new AutomationList (*this->_current.get()))
545                 );
546 }
547