13
0
livetrax/libs/ardour/automation_list.cc
Paul Davis 8402537493 use a lock to set/get AutomationList automation state
It isn't 100% clear that we should use the list's data lock, but it seems quite likely
that this is the correct design, because of the interlock between data being present
and automation state
2017-08-12 14:40:21 -04:00

603 lines
14 KiB
C++

/*
Copyright (C) 2002 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 <set>
#include <climits>
#include <float.h>
#include <cmath>
#include <sstream>
#include <algorithm>
#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 "pbd/i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
PBD::Signal1<void,AutomationList *> AutomationList::AutomationListCreated;
#if 0
static void dumpit (const AutomationList& al, string prefix = "")
{
cerr << prefix << &al << endl;
for (AutomationList::const_iterator i = al.begin(); i != al.end(); ++i) {
cerr << prefix << '\t' << (*i)->when << ',' << (*i)->value << endl;
}
cerr << "\n";
}
#endif
AutomationList::AutomationList (const Evoral::Parameter& id, const Evoral::ParameterDescriptor& desc)
: ControlList(id, desc)
, _before (0)
{
_state = Off;
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();
assert(_parameter.type() != NullAutomation);
AutomationListCreated(this);
}
AutomationList::AutomationList (const AutomationList& other)
: ControlList(other)
, StatefulDestructible()
, _before (0)
{
_state = other._state;
g_atomic_int_set (&_touching, other.touching());
create_curve_if_necessary();
assert(_parameter.type() != NullAutomation);
AutomationListCreated(this);
}
AutomationList::AutomationList (const AutomationList& other, double start, double end)
: ControlList(other, start, end)
, _before (0)
{
_state = other._state;
g_atomic_int_set (&_touching, other.touching());
create_curve_if_necessary();
assert(_parameter.type() != NullAutomation);
AutomationListCreated(this);
}
/** @param id is used for legacy sessions where the type is not present
* 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, ARDOUR::ParameterDescriptor(id))
, _before (0)
{
g_atomic_int_set (&_touching, 0);
_interpolation = default_interpolation ();
_state = Off;
set_state (node, Stateful::loading_state_version);
if (id) {
_parameter = id;
}
create_curve_if_necessary();
assert(_parameter.type() != NullAutomation);
AutomationListCreated(this);
}
AutomationList::~AutomationList()
{
delete _before;
}
boost::shared_ptr<Evoral::ControlList>
AutomationList::create(const Evoral::Parameter& id,
const Evoral::ParameterDescriptor& desc)
{
return boost::shared_ptr<Evoral::ControlList>(new AutomationList(id, desc));
}
void
AutomationList::create_curve_if_necessary()
{
switch (_parameter.type()) {
case GainAutomation:
case TrimAutomation:
case PanAzimuthAutomation:
case PanElevationAutomation:
case PanWidthAutomation:
case FadeInAutomation:
case FadeOutAutomation:
case EnvelopeAutomation:
create_curve();
break;
default:
break;
}
WritePassStarted.connect_same_thread (_writepass_connection, boost::bind (&AutomationList::snapshot_history, this, false));
}
AutomationList&
AutomationList::operator= (const AutomationList& other)
{
if (this != &other) {
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;
}
void
AutomationList::maybe_signal_changed ()
{
ControlList::maybe_signal_changed ();
if (!ControlList::frozen()) {
StateChanged (); /* EMIT SIGNAL */
}
}
AutoState
AutomationList::automation_state() const
{
Glib::Threads::RWLock::ReaderLock lm (Evoral::ControlList::_lock);
return _state;
}
void
AutomationList::set_automation_state (AutoState s)
{
{
Glib::Threads::RWLock::ReaderLock lm (Evoral::ControlList::_lock);
if (s == _state) {
return;
}
_state = s;
if (s == Write && _desc.toggled) {
snapshot_history (true);
}
}
automation_state_changed (s); /* EMIT SIGNAL */
}
Evoral::ControlList::InterpolationStyle
AutomationList::default_interpolation () const
{
switch (_parameter.type()) {
case GainAutomation:
case BusSendLevel:
case EnvelopeAutomation:
#ifndef XXX_NEW_INTERPOLATON__BREAK_SESSION_FORMAT_XXX
/* use old, wrong linear gain interpolation */
return ControlList::Linear;
#endif
return ControlList::Exponential;
break;
case TrimAutomation:
return ControlList::Logarithmic;
break;
default:
break;
}
/* based on Evoral::ParameterDescriptor log,toggle,.. */
return ControlList::default_interpolation ();
}
void
AutomationList::start_write_pass (double when)
{
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) {
start_write_pass (when);
}
g_atomic_int_set (&_touching, 1);
}
void
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);
}
/* _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 ()
{
ControlList::thaw();
if (_changed_when_thawed) {
_changed_when_thawed = false;
StateChanged(); /* EMIT SIGNAL */
}
}
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<AutomationList> (*this, before, after);
}
XMLNode&
AutomationList::get_state ()
{
return state (true, true);
}
XMLNode&
AutomationList::state (bool full, bool need_lock)
{
XMLNode* root = new XMLNode (X_("AutomationList"));
root->set_property ("automation-id", EventTypeMap::instance().to_symbol(_parameter));
root->set_property ("id", id());
#ifndef XXX_NEW_INTERPOLATON__BREAK_SESSION_FORMAT_XXX
/* force new enums to existing ones in session-file */
Evoral::ControlList::InterpolationStyle is = _interpolation;
switch (is) {
case ControlList::Exponential:
case ControlList::Logarithmic:
is = ControlList::Linear;
break;
default:
break;
}
root->set_property ("interpolation-style", is);
#else
root->set_property ("interpolation-style", _interpolation);
#endif
if (full) {
/* 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->set_property ("state", Off);
}
if (!_events.empty()) {
root->add_child_nocopy (serialize_events (need_lock));
}
return *root;
}
XMLNode&
AutomationList::serialize_events (bool need_lock)
{
XMLNode* node = new XMLNode (X_("events"));
stringstream str;
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 << PBD::to_string ((*xx)->when);
str << ' ';
str << PBD::to_string ((*xx)->value);
str << '\n';
}
/* XML is a bit wierd */
XMLNode* content_node = new XMLNode (X_("foo")); /* it gets renamed by libxml when we set content */
content_node->set_content (str.str());
node->add_child_nocopy (*content_node);
return *node;
}
int
AutomationList::deserialize_events (const XMLNode& node)
{
if (node.children().empty()) {
return -1;
}
XMLNode* content_node = node.children().front();
if (content_node->content().empty()) {
return -1;
}
ControlList::freeze ();
clear ();
stringstream str (content_node->content());
std::string x_str;
std::string y_str;
double x;
double y;
bool ok = true;
while (str) {
str >> x_str;
if (!str || !PBD::string_to<double> (x_str, x)) {
break;
}
str >> y_str;
if (!str || !PBD::string_to<double> (y_str, y)) {
ok = false;
break;
}
y = std::min ((double)_desc.upper, std::max ((double)_desc.lower, y));
fast_simple_add (x, y);
}
if (!ok) {
clear ();
error << _("automation list: cannot load coordinates from XML, all points ignored") << endmsg;
} else {
mark_dirty ();
maybe_signal_changed ();
}
thaw ();
return 0;
}
int
AutomationList::set_state (const XMLNode& node, int version)
{
XMLNodeList nlist = node.children();
XMLNode* nsos;
XMLNodeIterator niter;
if (node.name() == X_("events")) {
/* partial state setting*/
return deserialize_events (node);
}
if (node.name() == X_("Envelope") || node.name() == X_("FadeOut") || node.name() == X_("FadeIn")) {
if ((nsos = node.child (X_("AutomationList")))) {
/* new school in old school clothing */
return set_state (*nsos, version);
}
/* old school */
const XMLNodeList& elist = node.children();
XMLNodeConstIterator i;
ControlList::freeze ();
clear ();
for (i = elist.begin(); i != elist.end(); ++i) {
pframes_t x;
if (!(*i)->get_property ("x", x)) {
error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
continue;
}
double y;
if (!(*i)->get_property ("y", y)) {
error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
continue;
}
y = std::min ((double)_desc.upper, std::max ((double)_desc.lower, y));
fast_simple_add (x, y);
}
thaw ();
return 0;
}
if (node.name() != X_("AutomationList") ) {
error << string_compose (_("AutomationList: passed XML node called %1, not \"AutomationList\" - ignored"), node.name()) << endmsg;
return -1;
}
if (set_id (node)) {
/* update session AL list */
AutomationListCreated(this);
}
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." << endmsg;
}
if (!node.get_property (X_("interpolation-style"), _interpolation)) {
_interpolation = default_interpolation ();
}
#ifndef XXX_NEW_INTERPOLATON__BREAK_SESSION_FORMAT_XXX
/* internally force logarithmic and Trim params to use Log-scale */
if (_desc.logarithmic || _parameter.type() == TrimAutomation) {
_interpolation = ControlList::Logarithmic;
}
#endif
if (node.get_property (X_("state"), _state)) {
if (_state == Write) {
_state = Off;
}
automation_state_changed (_state);
} else {
_state = Off;
}
bool have_events = false;
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
if ((*niter)->name() == X_("events")) {
deserialize_events (*(*niter));
have_events = true;
}
}
if (!have_events) {
/* there was no Events child node; clear any current events */
freeze ();
clear ();
mark_dirty ();
maybe_signal_changed ();
thaw ();
}
return 0;
}
bool
AutomationList::operator!= (AutomationList const & other) const
{
return (
static_cast<ControlList const &> (*this) != static_cast<ControlList const &> (other) ||
_state != other._state ||
_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()))
);
}