dramatic overhaul of automation. too long to explain here. this work is not finished...
authorPaul Davis <paul@linuxaudiosystems.com>
Fri, 13 Jul 2012 21:05:45 +0000 (21:05 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Fri, 13 Jul 2012 21:05:45 +0000 (21:05 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@13038 d708f5d6-7413-0410-9779-e7cbd77b26cf

27 files changed:
gtk2_ardour/ardour_ui.cc
gtk2_ardour/ardour_ui_dialogs.cc
gtk2_ardour/automation_line.cc
gtk2_ardour/automation_line.h
gtk2_ardour/automation_time_axis.cc
libs/ardour/ardour/automatable.h
libs/ardour/ardour/automation_control.h
libs/ardour/ardour/debug.h
libs/ardour/ardour/rc_configuration_vars.h
libs/ardour/ardour/route.h
libs/ardour/audio_track.cc
libs/ardour/automatable.cc
libs/ardour/automation_control.cc
libs/ardour/automation_list.cc
libs/ardour/buffer_manager.cc
libs/ardour/debug.cc
libs/ardour/midi_track.cc
libs/ardour/plugin_insert.cc
libs/ardour/route.cc
libs/ardour/session_transport.cc
libs/ardour/track.cc
libs/ardour/wscript
libs/evoral/evoral/ControlList.hpp
libs/evoral/evoral/ControlSet.hpp
libs/evoral/evoral/types.hpp
libs/evoral/src/ControlList.cpp
libs/evoral/src/debug.cpp

index c5d2bf5c11152635a132bc3068d0d8a8fd292bdf..9c637761b0b4c767cc5d1958d799fd247a589dd9 100644 (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 ()
 {
index 7fc50df673aaf582020619112a702b98f284f619..0116f4e842d65cda93e01301892cc6c296db7604 100644 (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);
        }
index 4a21e7905a3dc51eef7e4fd7a8e02f9271e23546..6586aed966350a0fa79a5765784c4aea4e8d73e4 100644 (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 ()
 {
index 6be7ccd6b898cb1301fe4f3fd282f15c81f854f1..73734bcd40fc0fbbf0d01eee91325b78543e08c7 100644 (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;
 
index 7c9f38aa0f2a5fe5826eaaae143b20bf9075f942..da6b66994c3f7001bf091417cf67b4b5424c3dbb 100644 (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()) {
index dc86c0cdddc5298f9924d08eedc6b4b2e71a2465..6e0f7a97b58a55e04dafef39f4fd87cef63ae6bf 100644 (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);
index 2c15a1b1b0ebeaac72d7017a07898c370de9df59..10194b3f9be6bf25175d4b1184648e9b91e468c4 100644 (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;
index 5c72c9236f8c104e313a5c018dc491d4caf4012d..334aac53e6156dfe5eb96f646b227044dc516270 100644 (file)
@@ -60,6 +60,7 @@ namespace PBD {
                extern uint64_t TempoMath;
                extern uint64_t TempoMap;
                extern uint64_t OrderKeys;
+               extern uint64_t Automation;
        }
 }
 
index 37599b0ac6bf73d00c000ebb7fea23b71c3de446..09b0c55a74595f882c4551bac12f3005fdf16c7b 100644 (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)
index f6c737d766f03846addfb12d851d43d16073d508..22ecb19123c5041944fea1c71ee214a1bee6b7fe 100644 (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 { 
index 6161147f4406b443d185e002aa42ad21bfd6c456..4079cdb481153d26be627c9c73f5f59afa5e6ef0 100644 (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;
        }
index d0a605bcd9edbf80be131854d23ed37240a64eed..608ae745de36516575b705a71e950b4095e8d5db 100644 (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) {
+
+               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());
 
-               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());
+                       if (l) {
+                               l->start_write_pass (now);
                        }
                }
-
-               _last_automation_snapshot = now;
        }
 }
 
index 05463dcdd0ce79e5b40145181a1fc8734904804c..1fde567e28876bc71f755e398c7064eb64ad6668 100644 (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);
+}
index 26ca7b097a06bffe4d89163f8ea74f8eabff6e4a..39e7bacc4690451c732b07fcaa31295647cca2f6 100644 (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 
+                        */
                 }
         }
 }
index e3bc2cb97cf2895263cf6e7f011a15706cd9833c..71ff4946f3ff86303825724a452850b3164f8470 100644 (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
index db0f409d119f471488429c71c25bc4e855c997e8..0d0948d5766d39f24542c657068979deeabdb2fc 100644 (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");
 
 
index 1d352d622bcd5dbaa78f382cb3bb5c676f634770..800e7949d5a7b5f6694784e4df7739301fc2e427 100644 (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;
        }
index 6e7cfe373ffc7fef61bfb1482d02281be8b49aee..f390e190f5b96b9f064fcd317fc71dd5cdb29d58 100644 (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();
                 
index 1a6bff5096a1a6dbfbfd6aca3d880a5db593659e..80b738e003fd69c1e814fa90aaee3754df0f1d5b 100644 (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);
+               }
+       }
+}
index a878b9fabcc70ba850b0daab7c8c19a02f271b09..b5fb2a790fe299836d6ba35f4aefa84e57aa59ad 100644 (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()) {
index 0dc0a9f67629f38064f7c57f29869edf05f92269..452a0843e28f06d6189f2f7e9d9e487163d29e0d 100644 (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
index 04f67f7eb1d4e0fe276e1277dc0cb4dc8406569a..672dae6abd61a0598e567eda65b6915b84e630dd 100644 (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',
index 1e546e04fa829503a19ca6fb7d4f436437369288..c6127a4e9b6648590ffec2f1b5cc9aeacc3cb1f0 100644 (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
index 293d41175593d9079d7bef817e963e89e7754205..8df942b31fdb6b7f79fd31650e5dace455f14725 100644 (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;
index 35dec6de0bd5b60960bc4aeeaeb2500f9e144d4b..7bdbdc7a2e15b95bfb8885d1fa53b226f8ea4889 100644 (file)
@@ -52,6 +52,7 @@ namespace PBD {
        namespace DEBUG {
                extern uint64_t Sequence;
                extern uint64_t Note;
+               extern uint64_t ControlList;
        }
 }
 
index 9b672612ed269b2b65eba2004e365a11504ccaf2..9ae269453d7678399f952c9a26adbf1c69bd1933 100644 (file)
 #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);
-
-       ControlEvent* prevprev = 0;
-       ControlEvent* cur = 0;
-       ControlEvent* prev = 0;
-       iterator pprev;
-       int counter = 0;
+       bool changed = false;
 
-       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,37 +569,188 @@ 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;
                        }
-               }
 
-               if (insert) {
-                       _events.insert (insertion_point, new ControlEvent (when, value));
+                       /* 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.
+                        */
                }
 
+
                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;
                }
index 28f30d0be3811597b9de41b5608f142bdbdf9eff..9e82b93d1b2609c9d0c20d8df42f5515988c5290 100644 (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");