X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fautomation_list.cc;h=a081418017940f72ffe61d6926e3c8fd236a59e3;hb=1545c426d9e3bc0411f3b5532c0c5a9eb09394c8;hp=c8f5d18aec7377c4c01362842e6486d2359eac47;hpb=be85889464470e66e33f6f09f3cf1a64ef3c5063;p=ardour.git diff --git a/libs/ardour/automation_list.cc b/libs/ardour/automation_list.cc index c8f5d18aec..a081418017 100644 --- a/libs/ardour/automation_list.cc +++ b/libs/ardour/automation_list.cc @@ -24,12 +24,19 @@ #include #include #include "ardour/automation_list.h" +#include "ardour/beats_frames_converter.h" #include "ardour/event_type_map.h" +#include "ardour/parameter_descriptor.h" +#include "ardour/parameter_types.h" +#include "ardour/evoral_types_convert.h" +#include "ardour/types_convert.h" #include "evoral/Curve.hpp" +#include "pbd/memento_command.h" #include "pbd/stacktrace.h" #include "pbd/enumwriter.h" +#include "pbd/types_convert.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; @@ -47,12 +54,27 @@ static void dumpit (const AutomationList& al, string prefix = "") cerr << "\n"; } #endif -AutomationList::AutomationList (Evoral::Parameter id) - : ControlList(id) +AutomationList::AutomationList (const Evoral::Parameter& id, const Evoral::ParameterDescriptor& desc) + : ControlList(id, desc) + , _before (0) { _state = Off; - _style = Absolute; g_atomic_int_set (&_touching, 0); + _interpolation = default_interpolation (); + + create_curve_if_necessary(); + + assert(_parameter.type() != NullAutomation); + AutomationListCreated(this); +} + +AutomationList::AutomationList (const Evoral::Parameter& id) + : ControlList(id, ARDOUR::ParameterDescriptor(id)) + , _before (0) +{ + _state = Off; + g_atomic_int_set (&_touching, 0); + _interpolation = default_interpolation (); create_curve_if_necessary(); @@ -61,10 +83,10 @@ AutomationList::AutomationList (Evoral::Parameter id) } AutomationList::AutomationList (const AutomationList& other) - : StatefulDestructible() - , ControlList(other) + : ControlList(other) + , StatefulDestructible() + , _before (0) { - _style = other._style; _state = other._state; g_atomic_int_set (&_touching, other.touching()); @@ -76,8 +98,8 @@ AutomationList::AutomationList (const AutomationList& other) AutomationList::AutomationList (const AutomationList& other, double start, double end) : ControlList(other, start, end) + , _before (0) { - _style = other._style; _state = other._state; g_atomic_int_set (&_touching, other.touching()); @@ -91,11 +113,12 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl * in or below the AutomationList node. It is used if @param id is non-null. */ AutomationList::AutomationList (const XMLNode& node, Evoral::Parameter id) - : ControlList(id) + : ControlList(id, ARDOUR::ParameterDescriptor(id)) + , _before (0) { g_atomic_int_set (&_touching, 0); + _interpolation = default_interpolation (); _state = Off; - _style = Absolute; set_state (node, Stateful::loading_state_version); @@ -111,12 +134,14 @@ AutomationList::AutomationList (const XMLNode& node, Evoral::Parameter id) AutomationList::~AutomationList() { + delete _before; } boost::shared_ptr -AutomationList::create(Evoral::Parameter id) +AutomationList::create(const Evoral::Parameter& id, + const Evoral::ParameterDescriptor& desc) { - return boost::shared_ptr(new AutomationList(id)); + return boost::shared_ptr(new AutomationList(id, desc)); } void @@ -124,7 +149,10 @@ AutomationList::create_curve_if_necessary() { switch (_parameter.type()) { case GainAutomation: - case PanAutomation: + case TrimAutomation: + case PanAzimuthAutomation: + case PanElevationAutomation: + case PanWidthAutomation: case FadeInAutomation: case FadeOutAutomation: case EnvelopeAutomation: @@ -133,32 +161,22 @@ AutomationList::create_curve_if_necessary() default: break; } -} -bool -AutomationList::operator== (const AutomationList& other) -{ - return _events == other._events; + WritePassStarted.connect_same_thread (_writepass_connection, boost::bind (&AutomationList::snapshot_history, this, false)); } AutomationList& AutomationList::operator= (const AutomationList& other) { if (this != &other) { - - _events.clear (); - - for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) { - _events.push_back (new Evoral::ControlEvent (**i)); - } - - _min_yval = other._min_yval; - _max_yval = other._max_yval; - _max_xval = other._max_xval; - _default_value = other._default_value; - - mark_dirty (); - maybe_signal_changed (); + ControlList::freeze (); + /* ControlList::operator= calls copy_events() which calls + * mark_dirty() and maybe_signal_changed() + */ + ControlList::operator= (other); + _state = other._state; + _touching = other._touching; + ControlList::thaw (); } return *this; @@ -174,64 +192,109 @@ AutomationList::maybe_signal_changed () } } +AutoState +AutomationList::automation_state() const +{ + Glib::Threads::RWLock::ReaderLock lm (Evoral::ControlList::_lock); + return _state; +} + void AutomationList::set_automation_state (AutoState s) { - if (s != _state) { + { + Glib::Threads::RWLock::ReaderLock lm (Evoral::ControlList::_lock); + + if (s == _state) { + return; + } _state = s; + if (s == Write && _desc.toggled) { + snapshot_history (true); + } + } - if (_state == Write) { - Glib::Mutex::Lock lm (ControlList::_lock); - nascent.push_back (new NascentInfo (false)); - } + automation_state_changed (s); /* EMIT SIGNAL */ +} - automation_state_changed (s); /* EMIT SIGNAL */ +Evoral::ControlList::InterpolationStyle +AutomationList::default_interpolation () const +{ + switch (_parameter.type()) { + case GainAutomation: + case BusSendLevel: + case EnvelopeAutomation: + return ControlList::Exponential; + break; + case TrimAutomation: + return ControlList::Logarithmic; + break; + default: + break; } + /* based on Evoral::ParameterDescriptor log,toggle,.. */ + return ControlList::default_interpolation (); } void -AutomationList::set_automation_style (AutoStyle s) +AutomationList::start_write_pass (double when) { - if (s != _style) { - _style = s; - automation_style_changed (); /* EMIT SIGNAL */ - } + snapshot_history (true); + ControlList::start_write_pass (when); +} + +void +AutomationList::write_pass_finished (double when, double thinning_factor) +{ + ControlList::write_pass_finished (when, thinning_factor); } void AutomationList::start_touch (double when) { - if (_state == Touch) { - Glib::Mutex::Lock lm (ControlList::_lock); - nascent.push_back (new NascentInfo (true, when)); - } + if (_state == Touch) { + start_write_pass (when); + } g_atomic_int_set (&_touching, 1); } void -AutomationList::stop_touch (bool mark, double when) +AutomationList::stop_touch (double) { + if (g_atomic_int_get (&_touching) == 0) { + /* this touch has already been stopped (probably by Automatable::transport_stopped), + so we've nothing to do. + */ + return; + } + g_atomic_int_set (&_touching, 0); +} - if (_state == Touch) { - Glib::Mutex::Lock lm (ControlList::_lock); - - if (mark) { - nascent.back()->end_time = when; - - } else { - - /* nascent info created in start touch but never used. just get rid of it. - */ - - NascentInfo* ninfo = nascent.back (); - nascent.erase (nascent.begin()); - delete ninfo; - } - } +/* _before may be owned by the undo stack, + * so we have to be careful about doing this. + * + * ::before () transfers ownership, setting _before to 0 + */ +void +AutomationList::clear_history () +{ + delete _before; + _before = 0; } +void +AutomationList::snapshot_history (bool need_lock) +{ + if (!in_new_write_pass ()) { + return; + } + delete _before; + _before = &state (true, need_lock); +} + + void AutomationList::thaw () { @@ -243,69 +306,91 @@ AutomationList::thaw () } } +bool +AutomationList::paste (const ControlList& alist, double pos, DoubleBeatsFramesConverter const& bfc) +{ + AutomationType src_type = (AutomationType)alist.parameter().type(); + AutomationType dst_type = (AutomationType)_parameter.type(); + + if (parameter_is_midi (src_type) == parameter_is_midi (dst_type)) { + return ControlList::paste (alist, pos); + } + bool to_frame = parameter_is_midi (src_type); + + ControlList cl (alist); + cl.clear (); + for (const_iterator i = alist.begin ();i != alist.end (); ++i) { + double when = (*i)->when; + if (to_frame) { + when = bfc.to ((*i)->when); + } else { + when = bfc.from ((*i)->when); + } + cl.fast_simple_add (when, (*i)->value); + } + return ControlList::paste (cl, pos); +} + +Command* +AutomationList::memento_command (XMLNode* before, XMLNode* after) +{ + return new MementoCommand (*this, before, after); +} + XMLNode& AutomationList::get_state () { - return state (true); + return state (true, true); } XMLNode& -AutomationList::state (bool full) +AutomationList::state (bool full, bool need_lock) { XMLNode* root = new XMLNode (X_("AutomationList")); - char buf[64]; - LocaleGuard lg (X_("POSIX")); - - root->add_property ("automation-id", EventTypeMap::instance().to_symbol(_parameter)); - root->add_property ("id", _id.to_s()); - - snprintf (buf, sizeof (buf), "%.12g", _default_value); - root->add_property ("default", buf); - snprintf (buf, sizeof (buf), "%.12g", _min_yval); - root->add_property ("min-yval", buf); - snprintf (buf, sizeof (buf), "%.12g", _max_yval); - root->add_property ("max-yval", buf); - snprintf (buf, sizeof (buf), "%.12g", _max_xval); - root->add_property ("max-xval", buf); - - root->add_property ("interpolation-style", enum_2_string (_interpolation)); + root->set_property ("automation-id", EventTypeMap::instance().to_symbol(_parameter)); + root->set_property ("id", id()); + root->set_property ("interpolation-style", _interpolation); if (full) { - /* never serialize state with Write enabled - too dangerous - for the user's data - */ - if (_state != Write) { - root->add_property ("state", auto_state_to_string (_state)); - } else { - root->add_property ("state", auto_state_to_string (Off)); - } + /* never serialize state with Write enabled - too dangerous + for the user's data + */ + if (_state != Write) { + root->set_property ("state", _state); + } else { + if (_events.empty ()) { + root->set_property ("state", Off); + } else { + root->set_property ("state", Touch); + } + } } else { /* never save anything but Off for automation state to a template */ - root->add_property ("state", auto_state_to_string (Off)); + root->set_property ("state", Off); } - root->add_property ("style", auto_style_to_string (_style)); - if (!_events.empty()) { - root->add_child_nocopy (serialize_events()); + root->add_child_nocopy (serialize_events (need_lock)); } return *root; } XMLNode& -AutomationList::serialize_events () +AutomationList::serialize_events (bool need_lock) { XMLNode* node = new XMLNode (X_("events")); stringstream str; - str.precision(15); //10 digits is enough digits for 24 hours at 96kHz - + Glib::Threads::RWLock::ReaderLock lm (Evoral::ControlList::_lock, Glib::Threads::NOT_LOCK); + if (need_lock) { + lm.acquire (); + } for (iterator xx = _events.begin(); xx != _events.end(); ++xx) { - str << (double) (*xx)->when; + str << PBD::to_string ((*xx)->when); str << ' '; - str <<(double) (*xx)->value; + str << PBD::to_string ((*xx)->value); str << '\n'; } @@ -337,20 +422,23 @@ AutomationList::deserialize_events (const XMLNode& node) stringstream str (content_node->content()); + std::string x_str; + std::string y_str; double x; double y; bool ok = true; while (str) { - str >> x; - if (!str) { + str >> x_str; + if (!str || !PBD::string_to (x_str, x)) { break; } - str >> y; - if (!str) { + str >> y_str; + if (!str || !PBD::string_to (y_str, y)) { ok = false; break; } + y = std::min ((double)_desc.upper, std::max ((double)_desc.lower, y)); fast_simple_add (x, y); } @@ -373,7 +461,6 @@ AutomationList::set_state (const XMLNode& node, int version) XMLNodeList nlist = node.children(); XMLNode* nsos; XMLNodeIterator niter; - const XMLProperty* prop; if (node.name() == X_("events")) { /* partial state setting*/ @@ -391,31 +478,29 @@ AutomationList::set_state (const XMLNode& node, int version) const XMLNodeList& elist = node.children(); XMLNodeConstIterator i; - XMLProperty* prop; - nframes_t x; - double y; - ControlList::freeze (); + ControlList::freeze (); clear (); for (i = elist.begin(); i != elist.end(); ++i) { - if ((prop = (*i)->property ("x")) == 0) { + pframes_t x; + if (!(*i)->get_property ("x", x)) { error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg; continue; } - x = atoi (prop->value().c_str()); - if ((prop = (*i)->property ("y")) == 0) { + double y; + if (!(*i)->get_property ("y", y)) { error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg; continue; } - y = atof (prop->value().c_str()); + y = std::min ((double)_desc.upper, std::max ((double)_desc.lower, y)); fast_simple_add (x, y); } - thaw (); + thaw (); return 0; } @@ -425,65 +510,33 @@ AutomationList::set_state (const XMLNode& node, int version) return -1; } - if ((prop = node.property ("id")) != 0) { - _id = prop->value (); + if (set_id (node)) { /* update session AL list */ AutomationListCreated(this); } - if ((prop = node.property (X_("automation-id"))) != 0){ - _parameter = EventTypeMap::instance().new_parameter(prop->value()); + std::string value; + if (node.get_property (X_("automation-id"), value)) { + _parameter = EventTypeMap::instance().from_symbol(value); } else { - warning << "Legacy session: automation list has no automation-id property."; + warning << "Legacy session: automation list has no automation-id property." << endmsg; } - if ((prop = node.property (X_("interpolation-style"))) != 0) { - _interpolation = (InterpolationStyle)string_2_enum(prop->value(), _interpolation); - } else { - _interpolation = Linear; + if (!node.get_property (X_("interpolation-style"), _interpolation)) { + _interpolation = default_interpolation (); } - if ((prop = node.property (X_("default"))) != 0){ - _default_value = atof (prop->value().c_str()); - } else { - _default_value = 0.0; - } - - if ((prop = node.property (X_("style"))) != 0) { - _style = string_to_auto_style (prop->value()); - } else { - _style = Absolute; - } - - if ((prop = node.property (X_("state"))) != 0) { - _state = string_to_auto_state (prop->value()); - if (_state == Write) { - _state = Off; - } + if (node.get_property (X_("state"), _state)) { + if (_state == Write) { + _state = Off; + } + automation_state_changed (_state); } else { _state = Off; } - if ((prop = node.property (X_("min-yval"))) != 0) { - _min_yval = atof (prop->value ().c_str()); - } else { - _min_yval = FLT_MIN; - } - - if ((prop = node.property (X_("max-yval"))) != 0) { - _max_yval = atof (prop->value ().c_str()); - } else { - _max_yval = FLT_MAX; - } - - if ((prop = node.property (X_("max-xval"))) != 0) { - _max_xval = atof (prop->value ().c_str()); - } else { - _max_xval = 0; // means "no limit ; - } - bool have_events = false; - + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { if ((*niter)->name() == X_("events")) { deserialize_events (*(*niter)); @@ -503,3 +556,22 @@ AutomationList::set_state (const XMLNode& node, int version) return 0; } +bool +AutomationList::operator!= (AutomationList const & other) const +{ + return ( + static_cast (*this) != static_cast (other) || + _state != other._state || + _touching != other._touching + ); +} + +PBD::PropertyBase * +AutomationListProperty::clone () const +{ + return new AutomationListProperty ( + this->property_id(), + boost::shared_ptr (new AutomationList (*this->_old.get())), + boost::shared_ptr (new AutomationList (*this->_current.get())) + ); +}