Apply fix for 2546 from lincoln. Many thanks.
[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 <sigc++/bind.h>
27 #include <ardour/automation_list.h>
28 #include <ardour/event_type_map.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 sigc;
38 using namespace PBD;
39
40 sigc::signal<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.const_begin(); i != al.const_end(); ++i) {
47                 cerr << prefix << '\t' << (*i)->when << ',' << (*i)->value << endl;
48         }
49         cerr << "\n";
50 }
51 #endif
52
53 /* XXX: min_val max_val redundant? (param.min() param.max()) */
54 AutomationList::AutomationList (Evoral::Parameter id)
55         : ControlList(id)
56 {
57         _state = Off;
58         _style = Absolute;
59         _touching = false;
60
61         assert(_parameter.type() != NullAutomation);
62         AutomationListCreated(this);
63 }
64
65 AutomationList::AutomationList (const AutomationList& other)
66         : ControlList(other)
67 {
68         _style = other._style;
69         _state = other._state;
70         _touching = other._touching;
71         
72         assert(_parameter.type() != NullAutomation);
73         AutomationListCreated(this);
74 }
75
76 AutomationList::AutomationList (const AutomationList& other, double start, double end)
77         : ControlList(other, start, end)
78 {
79         _style = other._style;
80         _state = other._state;
81         _touching = other._touching;
82
83         assert(_parameter.type() != NullAutomation);
84         AutomationListCreated(this);
85 }
86
87 /** \a id is used for legacy sessions where the type is not present
88  * in or below the <AutomationList> node.  It is used if \a id is non-null.
89  */
90 AutomationList::AutomationList (const XMLNode& node, Evoral::Parameter id)
91         : ControlList(id)
92 {
93         _touching = false;
94         _state = Off;
95         _style = Absolute;
96         
97         set_state (node);
98
99         if (id)
100                 _parameter = id;
101
102         assert(_parameter.type() != NullAutomation);
103         AutomationListCreated(this);
104 }
105
106 AutomationList::~AutomationList()
107 {
108         GoingAway ();
109 }
110
111 boost::shared_ptr<Evoral::ControlList>
112 AutomationList::create(Evoral::Parameter id)
113 {
114         return boost::shared_ptr<Evoral::ControlList>(new AutomationList(id));
115 }
116
117 bool
118 AutomationList::operator== (const AutomationList& other)
119 {
120         return _events == other._events;
121 }
122
123 AutomationList&
124 AutomationList::operator= (const AutomationList& other)
125 {
126         if (this != &other) {
127                 
128                 _events.clear ();
129                 
130                 for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
131                         _events.push_back (new Evoral::ControlEvent (**i));
132                 }
133                 
134                 _min_yval = other._min_yval;
135                 _max_yval = other._max_yval;
136                 _max_xval = other._max_xval;
137                 _default_value = other._default_value;
138                 
139                 mark_dirty ();
140                 maybe_signal_changed ();
141         }
142
143         return *this;
144 }
145
146 void
147 AutomationList::maybe_signal_changed ()
148 {
149         ControlList::maybe_signal_changed ();
150
151         if (!_frozen) {
152                 StateChanged (); /* EMIT SIGNAL */
153         }
154 }
155
156 void
157 AutomationList::set_automation_state (AutoState s)
158 {
159         if (s != _state) {
160                 _state = s;
161                 automation_state_changed (); /* EMIT SIGNAL */
162         }
163 }
164
165 void
166 AutomationList::set_automation_style (AutoStyle s)
167 {
168         if (s != _style) {
169                 _style = s;
170                 automation_style_changed (); /* EMIT SIGNAL */
171         }
172 }
173
174 void
175 AutomationList::start_touch ()
176 {
177         _touching = true;
178         _new_value = true;
179 }
180
181 void
182 AutomationList::stop_touch ()
183 {
184         _touching = false;
185         _new_value = false;
186 }
187
188 void
189 AutomationList::freeze ()
190 {
191         _frozen++;
192 }
193
194 void
195 AutomationList::thaw ()
196 {
197         ControlList::thaw();
198
199         if (_changed_when_thawed) {
200                 StateChanged(); /* EMIT SIGNAL */
201         }
202 }
203
204 void 
205 AutomationList::mark_dirty () const
206 {
207         ControlList::mark_dirty ();
208         Dirty (); /* EMIT SIGNAL */
209 }
210
211 XMLNode&
212 AutomationList::get_state ()
213 {
214         return state (true);
215 }
216
217 XMLNode&
218 AutomationList::state (bool full)
219 {
220         XMLNode* root = new XMLNode (X_("AutomationList"));
221         char buf[64];
222         LocaleGuard lg (X_("POSIX"));
223
224         root->add_property ("automation-id", EventTypeMap::instance().to_symbol(_parameter));
225
226         root->add_property ("id", _id.to_s());
227
228         snprintf (buf, sizeof (buf), "%.12g", _default_value);
229         root->add_property ("default", buf);
230         snprintf (buf, sizeof (buf), "%.12g", _min_yval);
231         root->add_property ("min-yval", buf);
232         snprintf (buf, sizeof (buf), "%.12g", _max_yval);
233         root->add_property ("max-yval", buf);
234         snprintf (buf, sizeof (buf), "%.12g", _max_xval);
235         root->add_property ("max-xval", buf);
236
237         root->add_property ("interpolation-style", enum_2_string (_interpolation));
238
239         if (full) {
240                 root->add_property ("state", auto_state_to_string (_state));
241         } else {
242                 /* never save anything but Off for automation state to a template */
243                 root->add_property ("state", auto_state_to_string (Off));
244         }
245
246         root->add_property ("style", auto_style_to_string (_style));
247
248         if (!_events.empty()) {
249                 root->add_child_nocopy (serialize_events());
250         }
251
252         return *root;
253 }
254
255 XMLNode&
256 AutomationList::serialize_events ()
257 {
258         XMLNode* node = new XMLNode (X_("events"));
259         stringstream str;
260
261         for (iterator xx = _events.begin(); xx != _events.end(); ++xx) {
262                 str << (double) (*xx)->when;
263                 str << ' ';
264                 str <<(double) (*xx)->value;
265                 str << '\n';
266         }
267
268         /* XML is a bit wierd */
269
270         XMLNode* content_node = new XMLNode (X_("foo")); /* it gets renamed by libxml when we set content */
271         content_node->set_content (str.str());
272
273         node->add_child_nocopy (*content_node);
274
275         return *node;
276 }
277
278 int
279 AutomationList::deserialize_events (const XMLNode& node)
280 {
281         if (node.children().empty()) {
282                 return -1;
283         }
284
285         XMLNode* content_node = node.children().front();
286
287         if (content_node->content().empty()) {
288                 return -1;
289         }
290
291         freeze ();
292         clear ();
293         
294         stringstream str (content_node->content());
295         
296         double x;
297         double y;
298         bool ok = true;
299         
300         while (str) {
301                 str >> x;
302                 if (!str) {
303                         break;
304                 }
305                 str >> y;
306                 if (!str) {
307                         ok = false;
308                         break;
309                 }
310                 fast_simple_add (x, y);
311         }
312         
313         if (!ok) {
314                 clear ();
315                 error << _("automation list: cannot load coordinates from XML, all points ignored") << endmsg;
316         } else {
317                 mark_dirty ();
318                 reposition_for_rt_add (0);
319                 maybe_signal_changed ();
320         }
321
322         thaw ();
323
324         return 0;
325 }
326
327 int
328 AutomationList::set_state (const XMLNode& node)
329 {
330         XMLNodeList nlist = node.children();
331         XMLNode* nsos;
332         XMLNodeIterator niter;
333         const XMLProperty* prop;
334
335         if (node.name() == X_("events")) {
336                 /* partial state setting*/
337                 return deserialize_events (node);
338         }
339         
340         if (node.name() == X_("Envelope") || node.name() == X_("FadeOut") || node.name() == X_("FadeIn")) {
341
342                 if ((nsos = node.child (X_("AutomationList")))) {
343                         /* new school in old school clothing */
344                         return set_state (*nsos);
345                 }
346
347                 /* old school */
348
349                 const XMLNodeList& elist = node.children();
350                 XMLNodeConstIterator i;
351                 XMLProperty* prop;
352                 nframes_t x;
353                 double y;
354                 
355                 freeze ();
356                 clear ();
357                 
358                 for (i = elist.begin(); i != elist.end(); ++i) {
359                         
360                         if ((prop = (*i)->property ("x")) == 0) {
361                                 error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
362                                 continue;
363                         }
364                         x = atoi (prop->value().c_str());
365                         
366                         if ((prop = (*i)->property ("y")) == 0) {
367                                 error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
368                                 continue;
369                         }
370                         y = atof (prop->value().c_str());
371                         
372                         fast_simple_add (x, y);
373                 }
374                 
375                 thaw ();
376
377                 return 0;
378         }
379
380         if (node.name() != X_("AutomationList") ) {
381                 error << string_compose (_("AutomationList: passed XML node called %1, not \"AutomationList\" - ignored"), node.name()) << endmsg;
382                 return -1;
383         }
384
385         if ((prop = node.property ("id")) != 0) {
386                 _id = prop->value ();
387                 /* update session AL list */
388                 AutomationListCreated(this);
389         }
390         
391         if ((prop = node.property (X_("automation-id"))) != 0){ 
392                 _parameter = EventTypeMap::instance().new_parameter(prop->value());
393         } else {
394                 warning << "Legacy session: automation list has no automation-id property.";
395         }
396         
397         if ((prop = node.property (X_("interpolation-style"))) != 0) {
398                 _interpolation = (InterpolationStyle)string_2_enum(prop->value(), _interpolation);
399         } else {
400                 _interpolation = Linear;
401         }
402         
403         if ((prop = node.property (X_("default"))) != 0){ 
404                 _default_value = atof (prop->value().c_str());
405         } else {
406                 _default_value = 0.0;
407         }
408
409         if ((prop = node.property (X_("style"))) != 0) {
410                 _style = string_to_auto_style (prop->value());
411         } else {
412                 _style = Absolute;
413         }
414
415         if ((prop = node.property (X_("state"))) != 0) {
416                 _state = string_to_auto_state (prop->value());
417         } else {
418                 _state = Off;
419         }
420
421         if ((prop = node.property (X_("min_yval"))) != 0) {
422                 _min_yval = atof (prop->value ().c_str());
423         } else {
424                 _min_yval = FLT_MIN;
425         }
426
427         if ((prop = node.property (X_("max_yval"))) != 0) {
428                 _max_yval = atof (prop->value ().c_str());
429         } else {
430                 _max_yval = FLT_MAX;
431         }
432
433         if ((prop = node.property (X_("max_xval"))) != 0) {
434                 _max_xval = atof (prop->value ().c_str());
435         } else {
436                 _max_xval = 0; // means "no limit ;
437         }
438
439         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
440                 if ((*niter)->name() == X_("events")) {
441                         deserialize_events (*(*niter));
442                 }
443         }
444
445         return 0;
446 }
447