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