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