dramatic overhaul of automation. too long to explain here. this work is not finished - write/touch passes do not correctly overwrite existing data because the semantics of ControlList::insert_iterator need clarification. more to follow

git-svn-id: svn://localhost/ardour2/branches/3.0@13038 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2012-07-13 21:05:45 +00:00
parent b04cd7d704
commit 0532e2063b
27 changed files with 500 additions and 249 deletions

View file

@ -62,6 +62,7 @@
#include "ardour/ardour.h"
#include "ardour/audioengine.h"
#include "ardour/audiofilesource.h"
#include "ardour/automation_watch.h"
#include "ardour/diskstream.h"
#include "ardour/filename_extensions.h"
#include "ardour/port.h"
@ -878,6 +879,7 @@ ARDOUR_UI::ask_about_saving_session (const vector<string>& actions)
return -1;
}
gint
ARDOUR_UI::every_second ()
{

View file

@ -25,6 +25,7 @@
#include "ardour/session.h"
#include "ardour/audioengine.h"
#include "ardour/automation_watch.h"
#include "actions.h"
#include "add_route_dialog.h"
@ -77,6 +78,8 @@ ARDOUR_UI::set_session (Session *s)
}
}
AutomationWatch::instance().set_session (s);
if (location_ui->get()) {
location_ui->get()->set_session(s);
}

View file

@ -28,6 +28,8 @@
#include "ardour/automation_list.h"
#include "ardour/dB.h"
#include "ardour/debug.h"
#include "evoral/Curve.hpp"
#include "simplerect.h"
@ -79,6 +81,7 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv
_visible = Line;
update_pending = false;
have_timeout = false;
_uses_gain_mapping = false;
no_draw = false;
_is_boolean = false;
@ -123,15 +126,6 @@ AutomationLine::event_handler (GdkEvent* event)
return PublicEditor::instance().canvas_line_event (event, line, this);
}
void
AutomationLine::queue_reset ()
{
if (!update_pending) {
update_pending = true;
Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
}
}
void
AutomationLine::show ()
{
@ -829,7 +823,12 @@ void AutomationLine::set_colors ()
void
AutomationLine::list_changed ()
{
queue_reset ();
DEBUG_TRACE (DEBUG::Automation, string_compose ("\tline changed, existing update pending? %1\n", update_pending));
if (!update_pending) {
update_pending = true;
Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::queue_reset, this));
}
}
void
@ -936,7 +935,9 @@ AutomationLine::reset_callback (const Evoral::ControlList& events)
void
AutomationLine::reset ()
{
DEBUG_TRACE (DEBUG::Automation, "\t\tLINE RESET\n");
update_pending = false;
have_timeout = false;
if (no_draw) {
return;
@ -945,6 +946,27 @@ AutomationLine::reset ()
alist->apply_to_points (*this, &AutomationLine::reset_callback);
}
void
AutomationLine::queue_reset ()
{
/* this must be called from the GUI thread
*/
if (trackview.editor().session()->transport_rolling() && alist->automation_write()) {
/* automation write pass ... defer to a timeout */
/* redraw in 1/4 second */
if (!have_timeout) {
DEBUG_TRACE (DEBUG::Automation, "\tqueue timeout\n");
Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &AutomationLine::reset), false), 250);
have_timeout = true;
} else {
DEBUG_TRACE (DEBUG::Automation, "\ttimeout already queued, change ignored\n");
}
} else {
reset ();
}
}
void
AutomationLine::clear ()
{

View file

@ -166,12 +166,13 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
bool _our_time_converter;
VisibleAspects _visible;
bool _uses_gain_mapping : 1;
bool terminal_points_can_slide : 1;
bool update_pending : 1;
bool no_draw : 1;
bool _is_boolean : 1;
bool _uses_gain_mapping;
bool terminal_points_can_slide;
bool update_pending;
bool have_timeout;
bool no_draw;
bool _is_boolean;
/** true if we did a push at any point during the current drag */
bool did_push;

View file

@ -621,6 +621,11 @@ AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float t
AutomationSelection::iterator p;
boost::shared_ptr<AutomationList> alist(line.the_list());
if (_session->transport_rolling() && alist->automation_write()) {
/* do not paste if this control is in write mode and we're rolling */
return false;
}
for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
if (p == selection.lines.end()) {

View file

@ -45,7 +45,7 @@ public:
Automatable(Session&);
Automatable (const Automatable& other);
virtual ~Automatable() {}
virtual ~Automatable();
boost::shared_ptr<Evoral::Control>
control_factory(const Evoral::Parameter& id);
@ -59,7 +59,7 @@ public:
virtual void add_control(boost::shared_ptr<Evoral::Control>);
void clear_controls ();
virtual void automation_snapshot (framepos_t now, bool force);
virtual void transport_located (framepos_t now);
virtual void transport_stopped (framepos_t now);
virtual std::string describe_parameter(Evoral::Parameter param);

View file

@ -22,6 +22,8 @@
#define __ardour_automation_control_h__
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "pbd/controllable.h"
#include "evoral/Control.hpp"
#include "ardour/automation_list.h"
@ -34,7 +36,7 @@ class Automatable;
/** A PBD::Controllable with associated automation data (AutomationList)
*/
class AutomationControl : public PBD::Controllable, public Evoral::Control
class AutomationControl : public PBD::Controllable, public Evoral::Control, public boost::enable_shared_from_this<AutomationControl>
{
public:
AutomationControl(ARDOUR::Session&,
@ -42,37 +44,34 @@ public:
boost::shared_ptr<ARDOUR::AutomationList> l=boost::shared_ptr<ARDOUR::AutomationList>(),
const std::string& name="");
~AutomationControl ();
boost::shared_ptr<AutomationList> alist() const {
return boost::dynamic_pointer_cast<AutomationList>(_list);
}
void set_list(boost::shared_ptr<Evoral::ControlList>);
void set_list (boost::shared_ptr<Evoral::ControlList>);
inline bool automation_playback() const {
return ((ARDOUR::AutomationList*)_list.get())->automation_playback();
return alist()->automation_playback();
}
inline bool automation_write() const {
return ((ARDOUR::AutomationList*)_list.get())->automation_write();
return alist()->automation_write();
}
inline AutoState automation_state() const {
return ((ARDOUR::AutomationList*)_list.get())->automation_state();
return alist()->automation_state();
}
inline void set_automation_state(AutoState as) {
return ((ARDOUR::AutomationList*)_list.get())->set_automation_state(as);
inline AutoStyle automation_style() const {
return alist()->automation_style();
}
inline void start_touch(double when) {
set_touching (true);
return ((ARDOUR::AutomationList*)_list.get())->start_touch(when);
}
inline void stop_touch(bool mark, double when) {
set_touching (false);
return ((ARDOUR::AutomationList*)_list.get())->stop_touch(mark, when);
}
void set_automation_state(AutoState as);
void set_automation_style(AutoStyle as);
void start_touch (double when);
void stop_touch (bool mark, double when);
void set_value (double);
double get_value () const;

View file

@ -60,6 +60,7 @@ namespace PBD {
extern uint64_t TempoMath;
extern uint64_t TempoMap;
extern uint64_t OrderKeys;
extern uint64_t Automation;
}
}

View file

@ -152,7 +152,7 @@ CONFIG_VARIABLE (int32_t, history_depth, "history-depth", 20)
CONFIG_VARIABLE (bool, use_overlap_equivalency, "use-overlap-equivalency", false)
CONFIG_VARIABLE (bool, periodic_safety_backups, "periodic-safety-backups", true)
CONFIG_VARIABLE (uint32_t, periodic_safety_backup_interval, "periodic-safety-backup-interval", 120)
CONFIG_VARIABLE (float, automation_interval, "automation-interval", 50)
CONFIG_VARIABLE (float, automation_interval, "automation-interval", 500)
CONFIG_VARIABLE (bool, sync_all_route_ordering, "sync-all-route-ordering", true)
CONFIG_VARIABLE (bool, only_copy_imported_files, "only-copy-imported-files", false)
CONFIG_VARIABLE (bool, keep_tearoffs, "keep-tearoffs", false)

View file

@ -131,6 +131,7 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
virtual void nonrealtime_handle_transport_stopped (bool abort, bool did_locate, bool flush_processors);
virtual void realtime_handle_transport_stopped () {}
virtual void realtime_locate () {}
virtual void non_realtime_locate (framepos_t);
virtual void set_pending_declick (int);
/* end of vfunc-based API */
@ -409,7 +410,6 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
boost::shared_ptr<Processor> the_instrument() const;
InstrumentInfo& instrument_info() { return _instrument_info; }
void automation_snapshot (framepos_t now, bool force=false);
void protect_automation ();
enum {

View file

@ -321,8 +321,6 @@ AudioTrack::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_fram
framepos_t transport_frame;
boost::shared_ptr<AudioDiskstream> diskstream = audio_diskstream();
automation_snapshot (start_frame, false);
if (n_outputs().n_total() == 0 && _processors.empty()) {
return 0;
}

View file

@ -47,14 +47,12 @@ const string Automatable::xml_node_name = X_("Automation");
Automatable::Automatable(Session& session)
: _a_session(session)
, _last_automation_snapshot(0)
{
}
Automatable::Automatable (const Automatable& other)
: ControlSet (other)
, _a_session (other._a_session)
, _last_automation_snapshot (0)
{
Glib::Mutex::Lock lm (other._control_lock);
@ -63,6 +61,18 @@ Automatable::Automatable (const Automatable& other)
add_control (ac);
}
}
Automatable::~Automatable ()
{
{
Glib::Mutex::Lock lm (_control_lock);
for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
boost::dynamic_pointer_cast<AutomationControl>(li->second)->drop_references ();
}
}
}
int
Automatable::old_set_automation_state (const XMLNode& node)
{
@ -74,8 +84,6 @@ Automatable::old_set_automation_state (const XMLNode& node)
warning << _("Automation node has no path property") << endmsg;
}
_last_automation_snapshot = 0;
return 0;
}
@ -102,8 +110,6 @@ Automatable::load_automation (const string& path)
set<Evoral::Parameter> tosave;
controls().clear ();
_last_automation_snapshot = 0;
while (in) {
double when;
double value;
@ -228,8 +234,6 @@ Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter le
}
}
_last_automation_snapshot = 0;
return 0;
}
@ -258,11 +262,10 @@ Automatable::set_parameter_automation_state (Evoral::Parameter param, AutoState
{
Glib::Mutex::Lock lm (control_lock());
boost::shared_ptr<Evoral::Control> c = control (param, true);
boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
boost::shared_ptr<AutomationControl> c = automation_control (param, true);
if (s != l->automation_state()) {
l->set_automation_state (s);
if (c && (s != c->automation_state())) {
c->set_automation_state (s);
_a_session.set_dirty ();
}
}
@ -272,11 +275,10 @@ Automatable::get_parameter_automation_state (Evoral::Parameter param)
{
AutoState result = Off;
boost::shared_ptr<Evoral::Control> c = control(param);
boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
boost::shared_ptr<AutomationControl> c = automation_control(param);
if (c) {
result = l->automation_state();
result = c->automation_state();
}
return result;
@ -287,11 +289,10 @@ Automatable::set_parameter_automation_style (Evoral::Parameter param, AutoStyle
{
Glib::Mutex::Lock lm (control_lock());
boost::shared_ptr<Evoral::Control> c = control(param, true);
boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list());
boost::shared_ptr<AutomationControl> c = automation_control(param, true);
if (s != l->automation_style()) {
l->set_automation_style (s);
if (c && (s != c->automation_style())) {
c->set_automation_style (s);
_a_session.set_dirty ();
}
}
@ -336,19 +337,20 @@ Automatable::protect_automation ()
}
void
Automatable::automation_snapshot (framepos_t now, bool force)
Automatable::transport_located (framepos_t now)
{
if (force || _last_automation_snapshot > now || (now - _last_automation_snapshot) > _automation_interval) {
for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) {
for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
boost::shared_ptr<AutomationControl> c
= boost::dynamic_pointer_cast<AutomationControl>(i->second);
if (_a_session.transport_rolling() && c->automation_write()) {
c->list()->rt_add (now, i->second->user_double());
boost::shared_ptr<AutomationControl> c
= boost::dynamic_pointer_cast<AutomationControl>(li->second);
if (c) {
boost::shared_ptr<AutomationList> l
= boost::dynamic_pointer_cast<AutomationList>(c->list());
if (l) {
l->start_write_pass (now);
}
}
_last_automation_snapshot = now;
}
}

View file

@ -21,6 +21,7 @@
#include <iostream>
#include "ardour/automation_control.h"
#include "ardour/automation_watch.h"
#include "ardour/event_type_map.h"
#include "ardour/session.h"
@ -39,6 +40,10 @@ AutomationControl::AutomationControl(
{
}
AutomationControl::~AutomationControl ()
{
}
/** Get the current effective `user' value based on automation state */
double
AutomationControl::get_value() const
@ -52,17 +57,18 @@ AutomationControl::get_value() const
* @param value `user' value
*/
void
AutomationControl::set_value(double value)
AutomationControl::set_value (double value)
{
bool to_list = _list && _session.transport_stopped()
&& ((AutomationList*)_list.get())->automation_write();
bool to_list = _list && ((AutomationList*)_list.get())->automation_write();
if (to_list && parameter().toggled()) {
// store the previous value just before this so any
// interpolation works right
_list->add (get_double(), _session.transport_frame()-1);
bool erase_since_last = _session.transport_rolling();
_list->add (get_double(), _session.transport_frame()-1, erase_since_last);
}
Control::set_double (value, to_list, _session.transport_frame());
@ -72,9 +78,51 @@ AutomationControl::set_value(double value)
void
AutomationControl::set_list(boost::shared_ptr<Evoral::ControlList> list)
AutomationControl::set_list (boost::shared_ptr<Evoral::ControlList> list)
{
Control::set_list(list);
Control::set_list (list);
Changed(); /* EMIT SIGNAL */
}
void
AutomationControl::set_automation_state (AutoState as)
{
if (as != alist()->automation_state()) {
cerr << name() << " setting automation state to " << enum_2_string (as) << endl;
if (as == Write) {
AutomationWatch::instance().add_automation_watch (shared_from_this());
} else if (as == Touch) {
if (!touching()) {
AutomationWatch::instance().remove_automation_watch (shared_from_this());
}
} else {
AutomationWatch::instance().remove_automation_watch (shared_from_this());
}
alist()->set_automation_state (as);
}
}
void
AutomationControl::set_automation_style (AutoStyle as)
{
alist()->set_automation_style (as);
}
void
AutomationControl::start_touch(double when)
{
set_touching (true);
AutomationWatch::instance().add_automation_watch (shared_from_this());
alist()->start_touch(when);
}
void
AutomationControl::stop_touch(bool mark, double when)
{
set_touching (false);
AutomationWatch::instance().remove_automation_watch (shared_from_this());
alist()->stop_touch (mark, when);
}

View file

@ -180,11 +180,6 @@ AutomationList::set_automation_state (AutoState s)
{
if (s != _state) {
_state = s;
if (_state == Write) {
Glib::Mutex::Lock lm (ControlList::_lock);
nascent.push_back (new NascentInfo ());
}
automation_state_changed (s); /* EMIT SIGNAL */
}
}
@ -202,8 +197,7 @@ void
AutomationList::start_touch (double when)
{
if (_state == Touch) {
Glib::Mutex::Lock lm (ControlList::_lock);
nascent.push_back (new NascentInfo (when));
start_write_pass (when);
}
g_atomic_int_set (&_touching, 1);
@ -223,22 +217,11 @@ AutomationList::stop_touch (bool mark, double when)
if (_state == Touch) {
assert (!nascent.empty ());
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;
/* XXX need to mark the last added point with the
* current time
*/
}
}
}

View file

@ -48,7 +48,7 @@ BufferManager::init (uint32_t size)
thread_buffers->write (&ts, 1);
thread_buffers_list->push_back (ts);
}
cerr << "Initialized thread buffers, readable count now " << thread_buffers->read_space() << endl;
// cerr << "Initialized thread buffers, readable count now " << thread_buffers->read_space() << endl;
}
@ -59,7 +59,7 @@ BufferManager::get_thread_buffers ()
ThreadBuffers* tbp;
if (thread_buffers->read (&tbp, 1) == 1) {
cerr << "Got thread buffers, readable count now " << thread_buffers->read_space() << endl;
// cerr << "Got thread buffers, readable count now " << thread_buffers->read_space() << endl;
return tbp;
}
@ -71,7 +71,7 @@ BufferManager::put_thread_buffers (ThreadBuffers* tbp)
{
Glib::Mutex::Lock em (rb_mutex);
thread_buffers->write (&tbp, 1);
cerr << "Put back thread buffers, readable count now " << thread_buffers->read_space() << endl;
// cerr << "Put back thread buffers, readable count now " << thread_buffers->read_space() << endl;
}
void

View file

@ -57,5 +57,6 @@ uint64_t PBD::DEBUG::Layering = PBD::new_debug_bit ("layering");
uint64_t PBD::DEBUG::TempoMath = PBD::new_debug_bit ("tempomath");
uint64_t PBD::DEBUG::TempoMap = PBD::new_debug_bit ("tempomap");
uint64_t PBD::DEBUG::OrderKeys = PBD::new_debug_bit ("orderkeys");
uint64_t PBD::DEBUG::Automation = PBD::new_debug_bit ("automation");

View file

@ -283,8 +283,6 @@ MidiTrack::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame
boost::shared_ptr<MidiDiskstream> diskstream = midi_diskstream();
automation_snapshot (start_frame);
if (n_outputs().n_total() == 0 && _processors.empty()) {
return 0;
}

View file

@ -267,7 +267,7 @@ PluginInsert::parameter_changed (uint32_t which, float val)
boost::shared_ptr<AutomationControl> ac = automation_control (Evoral::Parameter (PluginAutomation, 0, which));
if (ac) {
ac->set_double (val);
ac->set_value (val);
Plugins::iterator i = _plugins.begin();

View file

@ -2930,10 +2930,6 @@ Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool did_lo
{
Glib::RWLock::ReaderLock lm (_processor_lock);
if (!did_locate) {
automation_snapshot (now, true);
}
Automatable::transport_stopped (now);
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
@ -3061,8 +3057,6 @@ Route::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, in
return 0;
}
automation_snapshot (_session.transport_frame(), false);
if (n_outputs().n_total() == 0) {
return 0;
}
@ -3274,18 +3268,6 @@ Route::set_latency_compensation (framecnt_t longest_session_latency)
}
}
void
Route::automation_snapshot (framepos_t now, bool force)
{
if (_pannable) {
_pannable->automation_snapshot (now, force);
}
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->automation_snapshot (now, force);
}
}
Route::SoloControllable::SoloControllable (std::string name, boost::shared_ptr<Route> r)
: AutomationControl (r->session(), Evoral::Parameter (SoloAutomation),
boost::shared_ptr<AutomationList>(), name)
@ -4174,3 +4156,19 @@ Route::the_instrument () const
}
return boost::shared_ptr<Processor>();
}
void
Route::non_realtime_locate (framepos_t pos)
{
if (_pannable) {
_pannable->transport_located (pos);
}
{
Glib::RWLock::WriterLock lm (_processor_lock);
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->transport_located (pos);
}
}
}

View file

@ -299,8 +299,8 @@ Session::butler_transport_work ()
if (tr) {
tr->adjust_playback_buffering ();
/* and refill those buffers ... */
tr->non_realtime_locate (_transport_frame);
}
(*i)->non_realtime_locate (_transport_frame);
}
}
@ -344,10 +344,8 @@ Session::butler_transport_work ()
if (!(ptw & PostTransportLocate)) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->hidden()) {
tr->non_realtime_locate (_transport_frame);
}
(*i)->non_realtime_locate (_transport_frame);
if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) {
/* new request, stop seeking, and start again */
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
@ -420,10 +418,7 @@ Session::non_realtime_locate ()
{
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr) {
tr->non_realtime_locate (_transport_frame);
}
(*i)->non_realtime_locate (_transport_frame);
}
/* XXX: it would be nice to generate the new clicks here (in the non-RT thread)
@ -601,10 +596,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n"));
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", (*i)->name()));
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->hidden()) {
tr->non_realtime_locate (_transport_frame);
}
(*i)->non_realtime_locate (_transport_frame);
if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) {
finished = false;
@ -1236,7 +1228,6 @@ Session::start_transport ()
if (tr) {
tr->realtime_set_speed (tr->speed(), true);
}
(*i)->automation_snapshot (_transport_frame, true);
}
if (!_engine.freewheeling()) {

View file

@ -620,7 +620,14 @@ Track::non_realtime_input_change ()
void
Track::non_realtime_locate (framepos_t p)
{
_diskstream->non_realtime_locate (p);
Route::non_realtime_locate (p);
if (!hidden()) {
/* don't waste i/o cycles and butler calls
for hidden (secret) tracks
*/
_diskstream->non_realtime_locate (p);
}
}
void

View file

@ -55,6 +55,7 @@ libardour_sources = [
'automation.cc',
'automation_control.cc',
'automation_list.cc',
'automation_watch.cc',
'beats_frames_converter.cc',
'broadcast_info.cc',
'buffer.cc',

View file

@ -124,10 +124,8 @@ public:
virtual bool clamp_value (double& /*when*/, double& /*value*/) const { return true; }
void rt_add (double when, double value);
void add (double when, double value);
void add (double when, double value, bool erase_since_last_add = false);
void fast_simple_add (double when, double value);
void merge_nascent (double when);
void erase_range (double start, double end);
void erase (iterator);
@ -245,6 +243,7 @@ public:
virtual bool touching() const { return false; }
virtual bool writing() const { return false; }
virtual bool touch_enabled() const { return false; }
void start_write_pass (double time);
void write_pass_finished (double when);
/** Emitted when mark_dirty() is called on this object */
@ -257,6 +256,8 @@ public:
bool operator!= (ControlList const &) const;
void invalidate_insert_iterator ();
protected:
/** Called by unlocked_eval() to handle cases of 3 or more control points. */
@ -287,21 +288,14 @@ protected:
Curve* _curve;
struct NascentInfo {
EventList events;
double start_time;
double end_time;
double same_value_cnt;
NascentInfo (double start = -1.0)
: start_time (start)
, end_time (-1.0)
, same_value_cnt (0)
{}
};
std::list<NascentInfo*> nascent;
static double _thinning_factor;
private:
iterator insert_iterator;
double insert_position;
bool new_write_pass;
bool did_write_during_pass;
void unlocked_invalidate_insert_iterator ();
};
} // namespace Evoral

View file

@ -38,7 +38,7 @@ class ControlSet : public boost::noncopyable {
public:
ControlSet();
ControlSet (const ControlSet&);
virtual ~ControlSet() {}
virtual ~ControlSet() {}
virtual boost::shared_ptr<Evoral::Control>
control_factory(const Evoral::Parameter& id) = 0;

View file

@ -52,6 +52,7 @@ namespace PBD {
namespace DEBUG {
extern uint64_t Sequence;
extern uint64_t Note;
extern uint64_t ControlList;
}
}

View file

@ -23,7 +23,10 @@
#include "evoral/ControlList.hpp"
#include "evoral/Curve.hpp"
#include "pbd/compose.h"
using namespace std;
using namespace PBD;
namespace Evoral {
@ -63,6 +66,10 @@ ControlList::ControlList (const Parameter& id)
_search_cache.left = -1;
_search_cache.first = _events.end();
_sort_pending = false;
new_write_pass = true;
did_write_during_pass = false;
insert_position = -1;
insert_iterator = _events.end();
}
ControlList::ControlList (const ControlList& other)
@ -78,6 +85,10 @@ ControlList::ControlList (const ControlList& other)
_lookup_cache.range.first = _events.end();
_search_cache.first = _events.end();
_sort_pending = false;
new_write_pass = true;
did_write_during_pass = false;
insert_position = -1;
insert_iterator = _events.end();
copy_events (other);
@ -106,6 +117,11 @@ ControlList::ControlList (const ControlList& other, double start, double end)
copy_events (*(section.get()));
}
new_write_pass = false;
did_write_during_pass = false;
insert_position = -1;
insert_iterator = _events.end();
mark_dirty ();
}
@ -115,13 +131,6 @@ ControlList::~ControlList()
delete (*x);
}
for (list<NascentInfo*>::iterator n = nascent.begin(); n != nascent.end(); ++n) {
for (EventList::iterator x = (*n)->events.begin(); x != (*n)->events.end(); ++x) {
delete *x;
}
delete (*n);
}
delete _curve;
}
@ -161,6 +170,7 @@ ControlList::copy_events (const ControlList& other)
for (const_iterator i = other.begin(); i != other.end(); ++i) {
_events.push_back (new ControlEvent ((*i)->when, (*i)->value));
}
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
maybe_signal_changed ();
@ -195,6 +205,7 @@ ControlList::clear ()
{
Glib::Mutex::Lock lm (_lock);
_events.clear ();
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
@ -233,16 +244,21 @@ ControlList::_x_scale (double factor)
void
ControlList::write_pass_finished (double when)
{
merge_nascent (when);
if (did_write_during_pass) {
thin ();
}
new_write_pass = true;
did_write_during_pass = false;
}
struct ControlEventTimeComparator {
bool operator() (ControlEvent* a, ControlEvent* b) {
return a->when < b->when;
}
};
#if 0
void
ControlList::merge_nascent (double when)
{
@ -432,96 +448,75 @@ ControlList::merge_nascent (double when)
maybe_signal_changed ();
}
void
ControlList::rt_add (double when, double value)
{
// this is for automation recording
if (touch_enabled() && !touching()) {
return;
}
// cerr << "RT: alist " << this << " add " << value << " @ " << when << endl;
Glib::Mutex::Lock lm (_lock, Glib::TRY_LOCK);
if (lm.locked()) {
assert (!nascent.empty());
/* we don't worry about adding events out of time order as we will
sort them in merge_nascent.
*/
NascentInfo* ni (nascent.back());
EventList& el (ni->events);
if (!el.empty() && (when >= el.back()->when) && (value == el.back()->value)) {
/* same value, later timestamp, effective slope is
* zero, so just move the last point in nascent to our
* new time position. this avoids storing an unlimited
* number of points to represent a flat line.
*/
ni->same_value_cnt++;
if (ni->same_value_cnt > 1) {
el.back()->when = when;
return;
}
} else {
ni->same_value_cnt = 0;
}
el.push_back (new ControlEvent (when, value));
}
}
#endif
void
ControlList::thin ()
{
Glib::Mutex::Lock lm (_lock);
bool changed = false;
ControlEvent* prevprev = 0;
ControlEvent* cur = 0;
ControlEvent* prev = 0;
iterator pprev;
int counter = 0;
for (iterator i = _events.begin(); i != _events.end(); ++i) {
cur = *i;
counter++;
if (counter > 2) {
{
Glib::Mutex::Lock lm (_lock);
ControlEvent* prevprev = 0;
ControlEvent* cur = 0;
ControlEvent* prev = 0;
iterator pprev;
int counter = 0;
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
for (iterator i = _events.begin(); i != _events.end(); ++i) {
double area = fabs ((prevprev->when * (prev->value - cur->value)) +
(prev->when * (cur->value - prevprev->value)) +
(cur->when * (prevprev->value - prev->value)));
cur = *i;
counter++;
if (area < _thinning_factor) {
iterator tmp = pprev;
/* pprev will change to current
i is incremented to the next event
*/
pprev = i;
_events.erase (tmp);
continue;
if (counter > 2) {
/* compute the area of the triangle formed by 3 points
*/
double area = fabs ((prevprev->when * (prev->value - cur->value)) +
(prev->when * (cur->value - prevprev->value)) +
(cur->when * (prevprev->value - prev->value)));
if (area < _thinning_factor) {
iterator tmp = pprev;
/* pprev will change to current
i is incremented to the next event
as we loop.
*/
pprev = i;
_events.erase (tmp);
changed = true;
continue;
}
}
prevprev = prev;
prev = cur;
pprev = i;
}
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
prevprev = prev;
prev = cur;
pprev = i;
if (changed) {
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
}
if (changed) {
maybe_signal_changed ();
}
}
void
ControlList::fast_simple_add (double when, double value)
{
Glib::Mutex::Lock lm (_lock);
/* to be used only for loading pre-sorted data from saved state */
_events.insert (_events.end(), new ControlEvent (when, value));
assert(_events.back());
@ -530,7 +525,41 @@ ControlList::fast_simple_add (double when, double value)
}
void
ControlList::add (double when, double value)
ControlList::invalidate_insert_iterator ()
{
Glib::Mutex::Lock lm (_lock);
unlocked_invalidate_insert_iterator ();
}
void
ControlList::unlocked_invalidate_insert_iterator ()
{
insert_iterator = _events.end();
}
void
ControlList::start_write_pass (double when)
{
Glib::Mutex::Lock lm (_lock);
new_write_pass = true;
did_write_during_pass = false;
insert_position = when;
ControlEvent cp (when, 0.0);
insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
if ((*insert_iterator)->when != when) {
/* doesn't point at a control point at precisely this time,
so reset it to the end and we'll find where to insert
if/when a new control event is added.
*/
unlocked_invalidate_insert_iterator ();
}
}
void
ControlList::add (double when, double value, bool erase_since_last_add)
{
/* this is for making changes from some kind of user interface or
control surface (GUI, MIDI, OSC etc)
@ -540,36 +569,187 @@ ControlList::add (double when, double value)
return;
}
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add %2 at %3 w/erase = %4\n", this, value, when, erase_since_last_add));
{
Glib::Mutex::Lock lm (_lock);
ControlEvent cp (when, 0.0f);
bool insert = true;
iterator insertion_point;
if (_events.empty()) {
/* as long as the point we're adding is not at zero,
* add an "anchor" point there.
*/
if (when > 1) {
_events.insert (_events.end(), new ControlEvent (0, _default_value));
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _default_value));
}
}
for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); insertion_point != _events.end(); ++insertion_point) {
if (new_write_pass) {
/* only one point allowed per time point */
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 new write pass, insert pos = %2, iter @ end ? %3\n",
this, insert_position, (insert_iterator == _events.end())));
/* The first addition of a new control event during a
* write pass.
*
* We need to add a new point at insert_position
* corresponding the value there.
*/
if ((*insertion_point)->when == when) {
(*insertion_point)->value = value;
insert = false;
break;
if (insert_iterator == _events.end()) {
/* the insert_iterator is not set, figure out where
* it needs to be.
*/
ControlEvent cp (insert_position, 0.0);
insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 looked up insert iterator for new write pass\n", this));
}
if ((*insertion_point)->when >= when) {
break;
double eval_value = unlocked_eval (insert_position);
if (insert_iterator == _events.end()) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
_events.push_back (new ControlEvent (insert_position, eval_value));
/* leave insert iterator at the end */
} else if ((*insert_iterator)->when == when) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
/* insert_iterator points to a control event
already at the insert position, so there is
nothing to do.
... except ...
advance insert_iterator so that the "real"
insert occurs in the right place, since it
points to the control event just inserted.
*/
++insert_iterator;
} else {
/* insert a new control event at the right spot
*/
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 at iterator\n", this, eval_value));
insert_iterator = _events.insert (insert_iterator, new ControlEvent (insert_position, eval_value));
/* advance insert_iterator so that the "real"
* insert occurs in the right place, since it
* points to the control event just inserted.
*/
++insert_iterator;
}
/* don't do this again till the next write pass */
new_write_pass = false;
did_write_during_pass = true;
} else if (insert_iterator == _events.end() || when > (*insert_iterator)->when) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 need to discover insert iterator (@end ? %2)\n",
this, (insert_iterator == _events.end())));
/* this means that we either *know* we want to insert
* at the end, or that we don't know where to insert.
*
* so ... lets perform some quick checks before we
* go doing binary search to figure out where to
* insert.
*/
if (_events.back()->when == when) {
/* we need to modify the final point, so
make insert_iterator point to it.
*/
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 modify final value\n", this));
insert_iterator = _events.end();
--insert_iterator;
} else if (_events.back()->when < when) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 plan to append to list\n", this));
if (erase_since_last_add) {
/* remove the final point, because
we're adding one beyond it.
*/
delete _events.back();
_events.pop_back();
}
/* leaving this here will force an append */
insert_iterator = _events.end();
} else {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 find based on lower bound, erase = %2\n", this, erase_since_last_add));
/* the new point is somewhere within the list,
* so figure out where to insert
*/
ControlEvent cp (when, 0.0);
insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
while (insert_iterator != _events.end()) {
if ((*insert_iterator)->when < when) {
if (erase_since_last_add) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*insert_iterator)));
delete *insert_iterator;
insert_iterator = _events.erase (insert_iterator);
continue;
}
} else if ((*insert_iterator)->when >= when) {
break;
}
++insert_iterator;
}
}
}
/* OK, now we're really ready to add a new point
*/
if (insert_iterator == _events.end()) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
_events.push_back (new ControlEvent (when, value));
/* leave insert_iterator as it was: at the end */
} else if ((*insert_iterator)->when == when) {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
/* only one point allowed per time point, so just
* reset the value here.
*/
(*insert_iterator)->value = value;
/* insert iterator now points past the control event we just
* modified. the next insert needs to be after this,
* so..
*/
++insert_iterator;
} else {
DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*insert_iterator)->when));
_events.insert (insert_iterator, new ControlEvent (when, value));
/* leave insert iterator where it was, since it points
* to the next control event AFTER the one we just inserted.
*/
}
if (insert) {
_events.insert (insertion_point, new ControlEvent (when, value));
}
mark_dirty ();
}
@ -582,6 +762,9 @@ ControlList::erase (iterator i)
{
{
Glib::Mutex::Lock lm (_lock);
if (insert_iterator == i) {
unlocked_invalidate_insert_iterator ();
}
_events.erase (i);
mark_dirty ();
}
@ -594,6 +777,7 @@ ControlList::erase (iterator start, iterator end)
{
Glib::Mutex::Lock lm (_lock);
_events.erase (start, end);
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
maybe_signal_changed ();
@ -613,6 +797,9 @@ ControlList::erase (double when, double value)
if (i != end ()) {
_events.erase (i);
if (insert_iterator == i) {
unlocked_invalidate_insert_iterator ();
}
}
mark_dirty ();
@ -654,6 +841,7 @@ ControlList::erase_range_internal (double start, double endt, EventList & events
e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
events.erase (s, e);
if (s != e) {
unlocked_invalidate_insert_iterator ();
erased = true;
}
}
@ -720,6 +908,7 @@ ControlList::modify (iterator iter, double when, double val)
if (!_frozen) {
_events.sort (event_time_less_than);
unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}
@ -783,6 +972,7 @@ ControlList::thaw ()
if (_sort_pending) {
_events.sort (event_time_less_than);
unlocked_invalidate_insert_iterator ();
_sort_pending = false;
}
}
@ -896,7 +1086,8 @@ ControlList::truncate_end (double last_coordinate)
_events.back()->when = last_coordinate;
_events.back()->value = last_val;
}
unlocked_invalidate_insert_iterator ();
mark_dirty();
}
@ -996,6 +1187,7 @@ ControlList::truncate_start (double overall_length)
_events.push_front (new ControlEvent (0, first_legal_value));
}
unlocked_invalidate_insert_iterator ();
mark_dirty();
}
@ -1437,6 +1629,7 @@ ControlList::cut_copy_clear (double start, double end, int op)
}
}
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
@ -1506,6 +1699,7 @@ ControlList::paste (ControlList& alist, double pos, float /*times*/)
}
}
unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
@ -1562,6 +1756,7 @@ ControlList::move_ranges (const list< RangeMove<double> >& movements)
if (!_frozen) {
_events.sort (event_time_less_than);
unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}

View file

@ -2,4 +2,5 @@
uint64_t PBD::DEBUG::Sequence = PBD::new_debug_bit ("sequence");
uint64_t PBD::DEBUG::Note = PBD::new_debug_bit ("note");
uint64_t PBD::DEBUG::ControlList = PBD::new_debug_bit ("controllist");