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