summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorCarl Hetherington <carl@carlh.net>2012-06-16 17:20:10 +0000
committerCarl Hetherington <carl@carlh.net>2012-06-16 17:20:10 +0000
commit5ac22e9095d5f851b5238029e3f813bc86fe2280 (patch)
tree51460c63aa9ace692dedceb01494f2a92d4bc364 /libs
parent9429401f11ffa0ee1729dbdc5e14e87cf06e2dc1 (diff)
Add new SharedStatefulProperty which manages a shared_ptr to
some Stateful object, and a subclass to use this for AutomationList. SharedStatefulProperty will manage undo / redo using full copies of the XML state, like MementoCommand, but does it within the Property undo system. git-svn-id: svn://localhost/ardour2/branches/3.0@12740 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs')
-rw-r--r--libs/ardour/ardour/audioregion.h41
-rw-r--r--libs/ardour/ardour/automation_list.h25
-rw-r--r--libs/ardour/audioregion.cc110
-rw-r--r--libs/ardour/automation_list.cc21
-rw-r--r--libs/ardour/globals.cc29
-rw-r--r--libs/ardour/test/automation_list_property_test.cc123
-rw-r--r--libs/ardour/test/automation_list_property_test.h32
-rw-r--r--libs/ardour/test/data/automation_list_property_test1.ref17
-rw-r--r--libs/ardour/test/data/automation_list_property_test2.ref24
-rw-r--r--libs/ardour/test/data/automation_list_property_test3.ref11
-rw-r--r--libs/ardour/test/data/automation_list_property_test4.ref13
-rw-r--r--libs/ardour/test/test_util.cc20
-rw-r--r--libs/ardour/test/test_util.h2
-rw-r--r--libs/ardour/wscript2
-rw-r--r--libs/evoral/evoral/ControlList.hpp2
-rw-r--r--libs/evoral/src/ControlList.cpp28
-rw-r--r--libs/pbd/pbd/properties.h115
-rw-r--r--libs/pbd/xml++.cc22
18 files changed, 524 insertions, 113 deletions
diff --git a/libs/ardour/ardour/audioregion.h b/libs/ardour/ardour/audioregion.h
index 5fdc0ac041..e38c98ea05 100644
--- a/libs/ardour/ardour/audioregion.h
+++ b/libs/ardour/ardour/audioregion.h
@@ -46,16 +46,11 @@ namespace Properties {
extern PBD::PropertyDescriptor<bool> fade_in_active;
extern PBD::PropertyDescriptor<bool> fade_out_active;
extern PBD::PropertyDescriptor<float> scale_amplitude;
-
- /* the envelope and fades are not scalar items and so
- currently (2010/02) are not stored using Property.
- However, these descriptors enable us to notify
- about changes to them via PropertyChange.
- */
-
- extern PBD::PropertyDescriptor<bool> envelope;
- extern PBD::PropertyDescriptor<bool> fade_in;
- extern PBD::PropertyDescriptor<bool> fade_out;
+ extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_in;
+ extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_in;
+ extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_out;
+ extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_out;
+ extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > envelope;
}
class Playlist;
@@ -99,11 +94,11 @@ class AudioRegion : public Region
bool fade_out_is_short() const { return _fade_out_is_short; }
void set_fade_out_is_short (bool yn);
- boost::shared_ptr<AutomationList> fade_in() { return _fade_in; }
- boost::shared_ptr<AutomationList> inverse_fade_in() { return _inverse_fade_in; }
- boost::shared_ptr<AutomationList> fade_out() { return _fade_out; }
- boost::shared_ptr<AutomationList> inverse_fade_out() { return _inverse_fade_out; }
- boost::shared_ptr<AutomationList> envelope() { return _envelope; }
+ boost::shared_ptr<AutomationList> fade_in() { return _fade_in.val (); }
+ boost::shared_ptr<AutomationList> inverse_fade_in() { return _inverse_fade_in.val (); }
+ boost::shared_ptr<AutomationList> fade_out() { return _fade_out.val (); }
+ boost::shared_ptr<AutomationList> inverse_fade_out() { return _inverse_fade_out.val (); }
+ boost::shared_ptr<AutomationList> envelope() { return _envelope.val (); }
Evoral::Range<framepos_t> body_range () const;
@@ -231,15 +226,15 @@ class AudioRegion : public Region
void connect_to_analysis_changed ();
void connect_to_header_position_offset_changed ();
- Automatable _automatable;
- boost::shared_ptr<AutomationList> _fade_in;
- boost::shared_ptr<AutomationList> _inverse_fade_in;
- boost::shared_ptr<AutomationList> _fade_out;
- boost::shared_ptr<AutomationList> _inverse_fade_out;
- boost::shared_ptr<AutomationList> _envelope;
- uint32_t _fade_in_suspended;
- uint32_t _fade_out_suspended;
+ AutomationListProperty _fade_in;
+ AutomationListProperty _inverse_fade_in;
+ AutomationListProperty _fade_out;
+ AutomationListProperty _inverse_fade_out;
+ AutomationListProperty _envelope;
+ Automatable _automatable;
+ uint32_t _fade_in_suspended;
+ uint32_t _fade_out_suspended;
boost::shared_ptr<ARDOUR::Region> get_single_other_xfade_region (bool start) const;
diff --git a/libs/ardour/ardour/automation_list.h b/libs/ardour/ardour/automation_list.h
index 58d1fe4acf..01c9d0641a 100644
--- a/libs/ardour/ardour/automation_list.h
+++ b/libs/ardour/ardour/automation_list.h
@@ -29,6 +29,7 @@
#include "pbd/undo.h"
#include "pbd/xml++.h"
#include "pbd/statefuldestructible.h"
+#include "pbd/properties.h"
#include "ardour/ardour.h"
@@ -36,6 +37,28 @@
namespace ARDOUR {
+class AutomationList;
+
+/** A SharedStatefulProperty for AutomationLists */
+class AutomationListProperty : public PBD::SharedStatefulProperty<AutomationList>
+{
+public:
+ AutomationListProperty (PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > d, Ptr p)
+ : PBD::SharedStatefulProperty<AutomationList> (d.property_id, p)
+ {}
+
+ AutomationListProperty (PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > d, Ptr o, Ptr c)
+ : PBD::SharedStatefulProperty<AutomationList> (d.property_id, o, c)
+ {}
+
+ PBD::PropertyBase* clone () const;
+
+private:
+ /* No copy-construction nor assignment */
+ AutomationListProperty (AutomationListProperty const &);
+ AutomationListProperty& operator= (AutomationListProperty const &);
+};
+
class AutomationList : public PBD::StatefulDestructible, public Evoral::ControlList
{
public:
@@ -82,6 +105,8 @@ class AutomationList : public PBD::StatefulDestructible, public Evoral::ControlL
XMLNode& state (bool full);
XMLNode& serialize_events ();
+ bool operator!= (const AutomationList &) const;
+
private:
void create_curve_if_necessary ();
int deserialize_events (const XMLNode&);
diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc
index 50a38b7bad..4b85819d6e 100644
--- a/libs/ardour/audioregion.cc
+++ b/libs/ardour/audioregion.cc
@@ -65,6 +65,11 @@ namespace ARDOUR {
PBD::PropertyDescriptor<bool> fade_out_is_short;
PBD::PropertyDescriptor<bool> fade_in_is_xfade;
PBD::PropertyDescriptor<bool> fade_in_is_short;
+ PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_in;
+ PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_in;
+ PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_out;
+ PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_out;
+ PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > envelope;
}
}
@@ -164,6 +169,16 @@ AudioRegion::make_property_quarks ()
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade-in-is-xfade = %1\n", Properties::fade_in_is_xfade.property_id));
Properties::fade_in_is_short.property_id = g_quark_from_static_string (X_("fade-in-is-short"));
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade-in-is-short = %1\n", Properties::fade_in_is_short.property_id));
+ Properties::fade_in.property_id = g_quark_from_static_string (X_("FadeIn"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for FadeIn = %1\n", Properties::fade_in.property_id));
+ Properties::inverse_fade_in.property_id = g_quark_from_static_string (X_("InverseFadeIn"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for InverseFadeIn = %1\n", Properties::inverse_fade_in.property_id));
+ Properties::fade_out.property_id = g_quark_from_static_string (X_("FadeOut"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for FadeOut = %1\n", Properties::fade_out.property_id));
+ Properties::inverse_fade_out.property_id = g_quark_from_static_string (X_("InverseFadeOut"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for InverseFadeOut = %1\n", Properties::inverse_fade_out.property_id));
+ Properties::envelope.property_id = g_quark_from_static_string (X_("Envelope"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for Envelope = %1\n", Properties::envelope.property_id));
}
void
@@ -181,6 +196,11 @@ AudioRegion::register_properties ()
add_property (_fade_out_is_short);
add_property (_fade_in_is_xfade);
add_property (_fade_in_is_short);
+ add_property (_fade_in);
+ add_property (_inverse_fade_in);
+ add_property (_fade_out);
+ add_property (_inverse_fade_out);
+ add_property (_envelope);
}
#define AUDIOREGION_STATE_DEFAULT \
@@ -193,7 +213,11 @@ AudioRegion::register_properties ()
, _fade_in_is_xfade (Properties::fade_in_is_xfade, false) \
, _fade_out_is_xfade (Properties::fade_out_is_xfade, false) \
, _fade_in_is_short (Properties::fade_in_is_short, false) \
- , _fade_out_is_short (Properties::fade_out_is_short, false)
+ , _fade_out_is_short (Properties::fade_out_is_short, false) \
+ , _fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))) \
+ , _inverse_fade_in (Properties::inverse_fade_in, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))) \
+ , _fade_out (Properties::fade_out, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeOutAutomation)))) \
+ , _inverse_fade_out (Properties::inverse_fade_out, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeOutAutomation))))
#define AUDIOREGION_COPY_STATE(other) \
_envelope_active (Properties::envelope_active, other->_envelope_active) \
@@ -205,7 +229,11 @@ AudioRegion::register_properties ()
, _fade_in_is_xfade (Properties::fade_in_is_xfade, other->_fade_in_is_xfade) \
, _fade_out_is_xfade (Properties::fade_out_is_xfade, other->_fade_out_is_xfade) \
, _fade_in_is_short (Properties::fade_in_is_short, other->_fade_in_is_short) \
- , _fade_out_is_short (Properties::fade_out_is_short, other->_fade_out_is_short)
+ , _fade_out_is_short (Properties::fade_out_is_short, other->_fade_out_is_short) \
+ , _fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_in.val()))) \
+ , _inverse_fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_in.val()))) \
+ , _fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_out.val()))) \
+ , _inverse_fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_out.val())))
/* a Session will reset these to its chosen defaults by calling AudioRegion::set_default_fade() */
void
@@ -227,12 +255,8 @@ AudioRegion::init ()
AudioRegion::AudioRegion (Session& s, framepos_t start, framecnt_t len, std::string name)
: Region (s, start, len, name, DataType::AUDIO)
, AUDIOREGION_STATE_DEFAULT
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter(EnvelopeAutomation))))
, _automatable (s)
- , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -244,12 +268,8 @@ AudioRegion::AudioRegion (Session& s, framepos_t start, framecnt_t len, std::str
AudioRegion::AudioRegion (const SourceList& srcs)
: Region (srcs)
, AUDIOREGION_STATE_DEFAULT
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter(EnvelopeAutomation))))
, _automatable(srcs[0]->session())
- , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -260,15 +280,11 @@ AudioRegion::AudioRegion (const SourceList& srcs)
AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
: Region (other)
, AUDIOREGION_COPY_STATE (other)
- , _automatable (other->session())
- , _fade_in (new AutomationList (*other->_fade_in))
- , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
- , _fade_out (new AutomationList (*other->_fade_out))
- , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
/* As far as I can see, the _envelope's times are relative to region position, and have nothing
to do with sources (and hence _start). So when we copy the envelope, we just use the supplied offset.
*/
- , _envelope (new AutomationList (*other->_envelope, 0, other->_length))
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val(), 0, other->_length)))
+ , _automatable (other->session())
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -286,15 +302,11 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t offset)
: Region (other, offset)
, AUDIOREGION_COPY_STATE (other)
- , _automatable (other->session())
- , _fade_in (new AutomationList (*other->_fade_in))
- , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
- , _fade_out (new AutomationList (*other->_fade_out))
- , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
/* As far as I can see, the _envelope's times are relative to region position, and have nothing
to do with sources (and hence _start). So when we copy the envelope, we just use the supplied offset.
*/
- , _envelope (new AutomationList (*other->_envelope, offset, other->_length))
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val(), offset, other->_length)))
+ , _automatable (other->session())
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -312,12 +324,8 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t
AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, const SourceList& srcs)
: Region (boost::static_pointer_cast<const Region>(other), srcs)
, AUDIOREGION_COPY_STATE (other)
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val())))
, _automatable (other->session())
- , _fade_in (new AutomationList (*other->_fade_in))
- , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
- , _fade_out (new AutomationList (*other->_fade_out))
- , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
- , _envelope (new AutomationList (*other->_envelope))
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -335,12 +343,8 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, const Sour
AudioRegion::AudioRegion (SourceList& srcs)
: Region (srcs)
, AUDIOREGION_STATE_DEFAULT
+ , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList(Evoral::Parameter(EnvelopeAutomation))))
, _automatable(srcs[0]->session())
- , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
- , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
- , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
, _fade_in_suspended (0)
, _fade_out_suspended (0)
{
@@ -988,7 +992,7 @@ void
AudioRegion::set_fade_in (boost::shared_ptr<AutomationList> f)
{
_fade_in->freeze ();
- *_fade_in = *f;
+ *(_fade_in.val()) = *f;
_fade_in->thaw ();
_default_fade_in = false;
@@ -1010,23 +1014,23 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
case FadeLinear:
_fade_in->fast_simple_add (0.0, 0.0);
_fade_in->fast_simple_add (len, 1.0);
- reverse_curve (_inverse_fade_in, _fade_in);
+ reverse_curve (_inverse_fade_in.val(), _fade_in.val());
break;
case FadeFast:
- generate_db_fade (_fade_in, len, 10, -60);
- reverse_curve (c1, _fade_in);
+ generate_db_fade (_fade_in.val(), len, 10, -60);
+ reverse_curve (c1, _fade_in.val());
_fade_in->copy_events (*c1);
- generate_inverse_power_curve (_inverse_fade_in, _fade_in);
+ generate_inverse_power_curve (_inverse_fade_in.val(), _fade_in.val());
break;
case FadeSlow:
generate_db_fade (c1, len, 10, -1); // start off with a slow fade
generate_db_fade (c2, len, 10, -80); // end with a fast fade
- merge_curves (_fade_in, c1, c2);
- reverse_curve (c3, _fade_in);
+ merge_curves (_fade_in.val(), c1, c2);
+ reverse_curve (c3, _fade_in.val());
_fade_in->copy_events (*c3);
- generate_inverse_power_curve (_inverse_fade_in, _fade_in);
+ generate_inverse_power_curve (_inverse_fade_in.val(), _fade_in.val());
break;
case FadeConstantPower:
@@ -1035,7 +1039,7 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
_fade_in->fast_simple_add (len*dist, sin (dist*M_PI/2));
}
_fade_in->fast_simple_add (len, 1.0);
- reverse_curve (_inverse_fade_in, _fade_in);
+ reverse_curve (_inverse_fade_in.val(), _fade_in.val());
break;
case FadeSymmetric:
@@ -1053,9 +1057,9 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
_fade_in->fast_simple_add (len* (breakpoint+((1.0-breakpoint)*(double)i/(double)num_steps)), coeff);
}
_fade_in->fast_simple_add (len, VERY_SMALL_SIGNAL);
- reverse_curve (c3, _fade_in);
+ reverse_curve (c3, _fade_in.val());
_fade_in->copy_events (*c3);
- reverse_curve (_inverse_fade_in, _fade_in );
+ reverse_curve (_inverse_fade_in.val(), _fade_in.val());
break;
}
@@ -1068,7 +1072,7 @@ void
AudioRegion::set_fade_out (boost::shared_ptr<AutomationList> f)
{
_fade_out->freeze ();
- *_fade_out = *f;
+ *(_fade_out.val()) = *f;
_fade_out->thaw ();
_default_fade_out = false;
@@ -1089,19 +1093,19 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
case FadeLinear:
_fade_out->fast_simple_add (0.0, 1.0);
_fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
- reverse_curve (_inverse_fade_out, _fade_out);
+ reverse_curve (_inverse_fade_out.val(), _fade_out.val());
break;
case FadeFast:
- generate_db_fade (_fade_out, len, 10, -60);
- generate_inverse_power_curve (_inverse_fade_out, _fade_out);
+ generate_db_fade (_fade_out.val(), len, 10, -60);
+ generate_inverse_power_curve (_inverse_fade_out.val(), _fade_out.val());
break;
case FadeSlow:
generate_db_fade (c1, len, 10, -1); //start off with a slow fade
generate_db_fade (c2, len, 10, -80); //end with a fast fade
- merge_curves (_fade_out, c1, c2);
- generate_inverse_power_curve (_inverse_fade_out, _fade_out);
+ merge_curves (_fade_out.val(), c1, c2);
+ generate_inverse_power_curve (_inverse_fade_out.val(), _fade_out.val());
break;
case FadeConstantPower:
@@ -1113,7 +1117,7 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
_fade_out->fast_simple_add ((len * dist), cos(dist*M_PI/2));
}
_fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
- reverse_curve (_inverse_fade_out, _fade_out);
+ reverse_curve (_inverse_fade_out.val(), _fade_out.val());
break;
case FadeSymmetric:
@@ -1132,7 +1136,7 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
_fade_out->fast_simple_add (len* (breakpoint+((1.0-breakpoint)*(double)i/(double)num_steps)), coeff);
}
_fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
- reverse_curve (_inverse_fade_out, _fade_out);
+ reverse_curve (_inverse_fade_out.val(), _fade_out.val());
break;
}
diff --git a/libs/ardour/automation_list.cc b/libs/ardour/automation_list.cc
index 54d838f6e2..26ca7b097a 100644
--- a/libs/ardour/automation_list.cc
+++ b/libs/ardour/automation_list.cc
@@ -510,3 +510,24 @@ AutomationList::set_state (const XMLNode& node, int version)
return 0;
}
+bool
+AutomationList::operator!= (AutomationList const & other) const
+{
+ return (
+ static_cast<ControlList const &> (*this) != static_cast<ControlList const &> (other) ||
+ _state != other._state ||
+ _style != other._style ||
+ _touching != other._touching
+ );
+}
+
+PBD::PropertyBase *
+AutomationListProperty::clone () const
+{
+ return new AutomationListProperty (
+ this->property_id(),
+ boost::shared_ptr<AutomationList> (new AutomationList (*this->_old.get())),
+ boost::shared_ptr<AutomationList> (new AutomationList (*this->_current.get()))
+ );
+}
+
diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc
index 35aef9dc86..1c6b98155c 100644
--- a/libs/ardour/globals.cc
+++ b/libs/ardour/globals.cc
@@ -115,34 +115,6 @@ extern void setup_enum_writer ();
*/
PBD::PropertyChange ARDOUR::bounds_change;
-namespace ARDOUR {
- namespace Properties {
-
- /* the envelope and fades are not scalar items and so
- currently (2010/02) are not stored using Property.
- However, these descriptors enable us to notify
- about changes to them via PropertyChange.
-
- Declared in ardour/audioregion.h ...
- */
-
- PBD::PropertyDescriptor<bool> fade_in;
- PBD::PropertyDescriptor<bool> fade_out;
- PBD::PropertyDescriptor<bool> envelope;
- }
-}
-
-void
-ARDOUR::make_property_quarks ()
-{
- Properties::fade_in.property_id = g_quark_from_static_string (X_("fade_in_FAKE"));
- DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade_in_FAKE = %1\n", Properties::fade_in.property_id));
- Properties::fade_out.property_id = g_quark_from_static_string (X_("fade_out_FAKE"));
- DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade_out_FAKE = %1\n", Properties::fade_out.property_id));
- Properties::envelope.property_id = g_quark_from_static_string (X_("envelope_FAKE"));
- DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for envelope_FAKE = %1\n", Properties::envelope.property_id));
-}
-
void
setup_hardware_optimization (bool try_optimization)
{
@@ -248,7 +220,6 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization)
PBD::ID::init ();
SessionEvent::init_event_pool ();
- make_property_quarks ();
SessionObject::make_property_quarks ();
Region::make_property_quarks ();
MidiRegion::make_property_quarks ();
diff --git a/libs/ardour/test/automation_list_property_test.cc b/libs/ardour/test/automation_list_property_test.cc
new file mode 100644
index 0000000000..9d1f448ad5
--- /dev/null
+++ b/libs/ardour/test/automation_list_property_test.cc
@@ -0,0 +1,123 @@
+/*
+ Copyright (C) 2012 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "pbd/properties.h"
+#include "pbd/stateful_diff_command.h"
+#include "ardour/automation_list.h"
+#include "automation_list_property_test.h"
+#include "test_util.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION (AutomationListPropertyTest);
+
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+void
+AutomationListPropertyTest::basicTest ()
+{
+ PropertyDescriptor<boost::shared_ptr<AutomationList> > descriptor;
+ descriptor.property_id = g_quark_from_static_string ("FadeIn");
+ AutomationListProperty property (
+ descriptor,
+ boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))
+ );
+
+ property.clear_changes ();
+
+ /* No change since we just cleared them */
+ CPPUNIT_ASSERT_EQUAL (false, property.changed());
+
+ property->add (1, 2);
+ property->add (3, 4);
+
+ /* Now it has changed */
+ CPPUNIT_ASSERT_EQUAL (true, property.changed());
+
+ XMLNode* foo = new XMLNode ("test");
+ property.get_changes_as_xml (foo);
+ check_xml (foo, "../libs/ardour/test/data/automation_list_property_test1.ref");
+
+ /* Do some more */
+ property.clear_changes ();
+ CPPUNIT_ASSERT_EQUAL (false, property.changed());
+ property->add (5, 6);
+ property->add (7, 8);
+ CPPUNIT_ASSERT_EQUAL (true, property.changed());
+ foo = new XMLNode ("test");
+ property.get_changes_as_xml (foo);
+ check_xml (foo, "../libs/ardour/test/data/automation_list_property_test2.ref");
+}
+
+/** Here's a StatefulDestructible class that has a AutomationListProperty */
+class Fred : public StatefulDestructible
+{
+public:
+ Fred ()
+ : _jim (_descriptor, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation))))
+
+ {
+ add_property (_jim);
+ }
+
+ XMLNode & get_state () {
+ XMLNode* n = new XMLNode ("State");
+ add_properties (*n);
+ return *n;
+ }
+
+ int set_state (XMLNode const & node, int) {
+ set_values (node);
+ return 0;
+ }
+
+ static void make_property_quarks () {
+ _descriptor.property_id = g_quark_from_static_string ("FadeIn");
+ }
+
+ AutomationListProperty _jim;
+ static PropertyDescriptor<boost::shared_ptr<AutomationList> > _descriptor;
+};
+
+PropertyDescriptor<boost::shared_ptr<AutomationList> > Fred::_descriptor;
+
+void
+AutomationListPropertyTest::undoTest ()
+{
+ Fred::make_property_quarks ();
+
+ boost::shared_ptr<Fred> sheila (new Fred);
+
+ /* Add some data */
+ sheila->_jim->add (1, 2);
+ sheila->_jim->add (3, 4);
+
+ /* Do a `command' */
+ sheila->clear_changes ();
+ sheila->_jim->add (5, 6);
+ sheila->_jim->add (7, 8);
+ StatefulDiffCommand sdc (sheila);
+
+ /* Undo */
+ sdc.undo ();
+ check_xml (&sheila->get_state(), "../libs/ardour/test/data/automation_list_property_test3.ref");
+
+ /* Redo */
+ sdc.redo ();
+ check_xml (&sheila->get_state(), "../libs/ardour/test/data/automation_list_property_test4.ref");
+}
diff --git a/libs/ardour/test/automation_list_property_test.h b/libs/ardour/test/automation_list_property_test.h
new file mode 100644
index 0000000000..8885d23242
--- /dev/null
+++ b/libs/ardour/test/automation_list_property_test.h
@@ -0,0 +1,32 @@
+/*
+ Copyright (C) 2012 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class AutomationListPropertyTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (AutomationListPropertyTest);
+ CPPUNIT_TEST (basicTest);
+ CPPUNIT_TEST (undoTest);
+ CPPUNIT_TEST_SUITE_END ();
+
+public:
+ void basicTest ();
+ void undoTest ();
+};
diff --git a/libs/ardour/test/data/automation_list_property_test1.ref b/libs/ardour/test/data/automation_list_property_test1.ref
new file mode 100644
index 0000000000..b7c11f153a
--- /dev/null
+++ b/libs/ardour/test/data/automation_list_property_test1.ref
@@ -0,0 +1,17 @@
+<test>
+ <FadeIn>
+ <from>
+ <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ </AutomationList>
+ </from>
+ <to>
+ <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ <events>
+ 1 2
+3 4
+
+ </events>
+ </AutomationList>
+ </to>
+ </FadeIn>
+</test>
diff --git a/libs/ardour/test/data/automation_list_property_test2.ref b/libs/ardour/test/data/automation_list_property_test2.ref
new file mode 100644
index 0000000000..17df0e4f54
--- /dev/null
+++ b/libs/ardour/test/data/automation_list_property_test2.ref
@@ -0,0 +1,24 @@
+<test>
+ <FadeIn>
+ <from>
+ <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ <events>
+ 1 2
+3 4
+
+ </events>
+ </AutomationList>
+ </from>
+ <to>
+ <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ <events>
+ 1 2
+3 4
+5 6
+7 8
+
+ </events>
+ </AutomationList>
+ </to>
+ </FadeIn>
+</test>
diff --git a/libs/ardour/test/data/automation_list_property_test3.ref b/libs/ardour/test/data/automation_list_property_test3.ref
new file mode 100644
index 0000000000..edc1469d10
--- /dev/null
+++ b/libs/ardour/test/data/automation_list_property_test3.ref
@@ -0,0 +1,11 @@
+<State>
+ <FadeIn>
+ <AutomationList automation-id="fadein" id="166" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ <events>
+ 1 2
+3 4
+
+ </events>
+ </AutomationList>
+ </FadeIn>
+</State>
diff --git a/libs/ardour/test/data/automation_list_property_test4.ref b/libs/ardour/test/data/automation_list_property_test4.ref
new file mode 100644
index 0000000000..5a6024a429
--- /dev/null
+++ b/libs/ardour/test/data/automation_list_property_test4.ref
@@ -0,0 +1,13 @@
+<State>
+ <FadeIn>
+ <AutomationList automation-id="fadein" id="166" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+ <events>
+ 1 2
+3 4
+5 6
+7 8
+
+ </events>
+ </AutomationList>
+ </FadeIn>
+</State>
diff --git a/libs/ardour/test/test_util.cc b/libs/ardour/test/test_util.cc
new file mode 100644
index 0000000000..f1237f72df
--- /dev/null
+++ b/libs/ardour/test/test_util.cc
@@ -0,0 +1,20 @@
+#include <fstream>
+#include <sstream>
+#include "pbd/xml++.h"
+#include <cppunit/extensions/HelperMacros.h>
+
+using namespace std;
+
+void
+check_xml (XMLNode* node, string ref_file)
+{
+ system ("rm -f libs/ardour/test/test.xml");
+ ofstream f ("libs/ardour/test/test.xml");
+ node->dump (f);
+ f.close ();
+
+ stringstream cmd;
+ cmd << "diff -u libs/ardour/test/test.xml " << ref_file;
+ CPPUNIT_ASSERT_EQUAL (0, system (cmd.str().c_str ()));
+}
+
diff --git a/libs/ardour/test/test_util.h b/libs/ardour/test/test_util.h
new file mode 100644
index 0000000000..84766dd3a1
--- /dev/null
+++ b/libs/ardour/test/test_util.h
@@ -0,0 +1,2 @@
+
+extern void check_xml (XMLNode *, std::string);
diff --git a/libs/ardour/wscript b/libs/ardour/wscript
index c16c0469ec..2eb279b535 100644
--- a/libs/ardour/wscript
+++ b/libs/ardour/wscript
@@ -424,10 +424,12 @@ def build(bld):
testobj = bld(features = 'cxx cxxprogram')
testobj.source = '''
test/dummy_lxvst.cc
+ test/test_util.cc
test/test_needing_session.cc
test/audio_region_test.cc
test/test_globals.cc
test/audio_region_read_test.cc
+ test/automation_list_property_test.cc
test/bbt_test.cc
test/tempo_test.cc
test/interpolation_test.cc
diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp
index 40e2e54625..b1ec7897cf 100644
--- a/libs/evoral/evoral/ControlList.hpp
+++ b/libs/evoral/evoral/ControlList.hpp
@@ -255,6 +255,8 @@ public:
static void set_thinning_factor (double d);
static double thinning_factor() { return _thinning_factor; }
+ bool operator!= (ControlList const &) const;
+
protected:
/** Called by unlocked_eval() to handle cases of 3 or more control points. */
diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp
index 9a820294ef..53200398a5 100644
--- a/libs/evoral/src/ControlList.cpp
+++ b/libs/evoral/src/ControlList.cpp
@@ -1529,5 +1529,33 @@ ControlList::set_thinning_factor (double v)
_thinning_factor = v;
}
+bool
+ControlList::operator!= (ControlList const & other) const
+{
+ if (_events.size() != other._events.size()) {
+ return true;
+ }
+
+ EventList::const_iterator i = _events.begin ();
+ EventList::const_iterator j = other._events.begin ();
+
+ while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
+ ++i;
+ ++j;
+ }
+
+ if (i != _events.end ()) {
+ return true;
+ }
+
+ return (
+ _parameter != other._parameter ||
+ _interpolation != other._interpolation ||
+ _min_yval != other._min_yval ||
+ _max_yval != other._max_yval ||
+ _default_value != other._default_value
+ );
+}
+
} // namespace Evoral
diff --git a/libs/pbd/pbd/properties.h b/libs/pbd/pbd/properties.h
index d961046760..e65929c60c 100644
--- a/libs/pbd/pbd/properties.h
+++ b/libs/pbd/pbd/properties.h
@@ -30,6 +30,7 @@
#include "pbd/property_basics.h"
#include "pbd/property_list.h"
#include "pbd/enumwriter.h"
+#include "pbd/stateful.h"
namespace PBD {
@@ -341,7 +342,121 @@ private:
/* no copy-construction */
EnumProperty (EnumProperty const &);
};
+
+/** A Property which holds a shared_ptr to a Stateful object,
+ * and handles undo using the somewhat inefficient approach
+ * of saving the complete XML state of its object before and
+ * after changes. A sort of half-way house between the old
+ * complete-state undo system and the new difference-based
+ * one.
+ */
+template <class T>
+class SharedStatefulProperty : public PropertyBase
+{
+public:
+ typedef boost::shared_ptr<T> Ptr;
+ SharedStatefulProperty (PropertyID d, Ptr p)
+ : PropertyBase (d)
+ , _current (p)
+ {
+
+ }
+
+ SharedStatefulProperty (PropertyID d, Ptr o, Ptr c)
+ : PropertyBase (d)
+ , _old (o)
+ , _current (c)
+ {
+
+ }
+
+ bool set_value (XMLNode const & node) {
+
+ /* Look for our node */
+ XMLNode* n = node.child (property_name ());
+ if (!n) {
+ return false;
+ }
+
+ /* And there should be one child which is the state of our T */
+ XMLNodeList const & children = n->children ();
+ if (children.size() != 1) {
+ return false;
+ }
+
+ _current->set_state (*children.front (), Stateful::current_state_version);
+ return true;
+ }
+
+ void get_value (XMLNode & node) const {
+ XMLNode* n = node.add_child (property_name ());
+ n->add_child_nocopy (_current->get_state ());
+ }
+
+ void clear_changes () {
+ /* We are starting to change things, so _old gets set up
+ with the current state.
+ */
+ _old.reset (new T (*_current.get()));
+ }
+
+ bool changed () const {
+ /* Expensive, but, hey; this requires operator!= in
+ our T
+ */
+ return (*_old != *_current);
+ }
+
+ void invert () {
+ _current.swap (_old);
+ }
+
+ void get_changes_as_xml (XMLNode* history_node) const {
+ /* We express the diff as before and after state, just
+ as MementoCommand does.
+ */
+ XMLNode* p = history_node->add_child (property_name ());
+ XMLNode* from = p->add_child ("from");
+ from->add_child_nocopy (_old->get_state ());
+ XMLNode* to = p->add_child ("to");
+ to->add_child_nocopy (_current->get_state ());
+ }
+
+ void get_changes_as_properties (PropertyList& changes, Command *) const {
+ if (changed ()) {
+ changes.add (clone ());
+ }
+ }
+
+ void apply_changes (PropertyBase const * p) {
+ *_current = *(dynamic_cast<SharedStatefulProperty const *> (p))->val ();
+ }
+
+ Ptr val () const {
+ return _current;
+ }
+
+ T* operator-> () const {
+ return _current.operator-> ();
+ }
+
+ operator bool () const {
+ return _current;
+ }
+
+protected:
+
+ Ptr _old;
+ Ptr _current;
+
+private:
+
+ /* No copy-construction nor assignment */
+ SharedStatefulProperty (SharedStatefulProperty<T> const &);
+ SharedStatefulProperty<T>& operator= (SharedStatefulProperty<T> const &);
+};
+
} /* namespace PBD */
#include "pbd/property_list_impl.h"
diff --git a/libs/pbd/xml++.cc b/libs/pbd/xml++.cc
index e2ccd67738..3046f971ab 100644
--- a/libs/pbd/xml++.cc
+++ b/libs/pbd/xml++.cc
@@ -652,13 +652,19 @@ static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
void
XMLNode::dump (ostream& s, string p) const
{
- s << p << _name << " ";
- for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
- s << (*i)->name() << "=" << (*i)->value() << " ";
- }
- s << "\n";
-
- for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
- (*i)->dump (s, p + " ");
+ if (_is_content) {
+ s << p << " " << content() << "\n";
+ } else {
+ s << p << "<" << _name;
+ for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
+ s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
+ }
+ s << ">\n";
+
+ for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
+ (*i)->dump (s, p + " ");
+ }
+
+ s << p << "</" << _name << ">\n";
}
}