#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"
return -1;
}
+
gint
ARDOUR_UI::every_second ()
{
#include "ardour/session.h"
#include "ardour/audioengine.h"
+#include "ardour/automation_watch.h"
#include "actions.h"
#include "add_route_dialog.h"
}
}
+ AutomationWatch::instance().set_session (s);
+
if (location_ui->get()) {
location_ui->get()->set_session(s);
}
#include "ardour/automation_list.h"
#include "ardour/dB.h"
+#include "ardour/debug.h"
+
#include "evoral/Curve.hpp"
#include "simplerect.h"
_visible = Line;
update_pending = false;
+ have_timeout = false;
_uses_gain_mapping = false;
no_draw = false;
_is_boolean = false;
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 ()
{
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
void
AutomationLine::reset ()
{
+ DEBUG_TRACE (DEBUG::Automation, "\t\tLINE RESET\n");
update_pending = false;
+ have_timeout = false;
if (no_draw) {
return;
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 ()
{
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;
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()) {
Automatable(Session&);
Automatable (const Automatable& other);
- virtual ~Automatable() {}
+ virtual ~Automatable();
boost::shared_ptr<Evoral::Control>
control_factory(const Evoral::Parameter& id);
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);
#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"
/** 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&,
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;
extern uint64_t TempoMath;
extern uint64_t TempoMap;
extern uint64_t OrderKeys;
+ extern uint64_t Automation;
}
}
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)
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 */
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 {
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;
}
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);
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)
{
warning << _("Automation node has no path property") << endmsg;
}
- _last_automation_snapshot = 0;
-
return 0;
}
set<Evoral::Parameter> tosave;
controls().clear ();
- _last_automation_snapshot = 0;
-
while (in) {
double when;
double value;
}
}
- _last_automation_snapshot = 0;
-
return 0;
}
{
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 ();
}
}
{
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;
{
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 ();
}
}
}
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;
}
}
#include <iostream>
#include "ardour/automation_control.h"
+#include "ardour/automation_watch.h"
#include "ardour/event_type_map.h"
#include "ardour/session.h"
{
}
+AutomationControl::~AutomationControl ()
+{
+}
+
/** Get the current effective `user' value based on automation state */
double
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());
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);
+}
{
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 */
}
}
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);
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
+ */
}
}
}
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;
}
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;
}
{
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
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");
boost::shared_ptr<MidiDiskstream> diskstream = midi_diskstream();
- automation_snapshot (start_frame);
-
if (n_outputs().n_total() == 0 && _processors.empty()) {
return 0;
}
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();
{
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) {
return 0;
}
- automation_snapshot (_session.transport_frame(), false);
-
if (n_outputs().n_total() == 0) {
return 0;
}
}
}
-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)
}
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);
+ }
+ }
+}
if (tr) {
tr->adjust_playback_buffering ();
/* and refill those buffers ... */
- tr->non_realtime_locate (_transport_frame);
}
+ (*i)->non_realtime_locate (_transport_frame);
}
}
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);
{
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)
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;
if (tr) {
tr->realtime_set_speed (tr->speed(), true);
}
- (*i)->automation_snapshot (_transport_frame, true);
}
if (!_engine.freewheeling()) {
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
'automation.cc',
'automation_control.cc',
'automation_list.cc',
+ 'automation_watch.cc',
'beats_frames_converter.cc',
'broadcast_info.cc',
'buffer.cc',
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);
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 */
bool operator!= (ControlList const &) const;
+ void invalidate_insert_iterator ();
+
protected:
/** Called by unlocked_eval() to handle cases of 3 or more control points. */
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
public:
ControlSet();
ControlSet (const ControlSet&);
- virtual ~ControlSet() {}
+ virtual ~ControlSet() {}
virtual boost::shared_ptr<Evoral::Control>
control_factory(const Evoral::Parameter& id) = 0;
namespace DEBUG {
extern uint64_t Sequence;
extern uint64_t Note;
+ extern uint64_t ControlList;
}
}
#include "evoral/ControlList.hpp"
#include "evoral/Curve.hpp"
+#include "pbd/compose.h"
+
using namespace std;
+using namespace PBD;
namespace Evoral {
_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)
_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);
copy_events (*(section.get()));
}
+ new_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ insert_iterator = _events.end();
+
mark_dirty ();
}
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;
}
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 ();
{
Glib::Mutex::Lock lm (_lock);
_events.clear ();
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
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)
{
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());
}
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)
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 ();
}
{
{
Glib::Mutex::Lock lm (_lock);
+ if (insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
+ }
_events.erase (i);
mark_dirty ();
}
{
Glib::Mutex::Lock lm (_lock);
_events.erase (start, end);
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
maybe_signal_changed ();
if (i != end ()) {
_events.erase (i);
+ if (insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
+ }
}
mark_dirty ();
e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
events.erase (s, e);
if (s != e) {
+ unlocked_invalidate_insert_iterator ();
erased = true;
}
}
if (!_frozen) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}
if (_sort_pending) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
_sort_pending = false;
}
}
_events.back()->when = last_coordinate;
_events.back()->value = last_val;
}
-
+
+ unlocked_invalidate_insert_iterator ();
mark_dirty();
}
_events.push_front (new ControlEvent (0, first_legal_value));
}
+ unlocked_invalidate_insert_iterator ();
mark_dirty();
}
}
}
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
}
}
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
if (!_frozen) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}
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");