--- a/gtk2_ardour/ardour.menus
+++ b/gtk2_ardour/ardour.menus
@@ -555,6 +555,7 @@
                	     <menuitem action='RubberbandingSnapsToGrid'/>
                	     <menuitem action='AutoAnalyseAudio'/>
                	     <menuitem action='toggle-region-fades'/>
+		     <menuitem action='automation-follows-regions'/>
 		</menu>
          </menu>
 	 <menu name='Help' action='Help'>
diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc
index 854eb57..06510b1 100644
--- a/gtk2_ardour/automation_line.cc
+++ b/gtk2_ardour/automation_line.cc
@@ -1102,12 +1102,6 @@ AutomationLine::change_model (AutomationList::iterator i, double x, double y)
 }
 
 void
-AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
-{
-	alist->move_range (start, end, xdelta, ydelta);
-}
-
-void
 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
 {
 	alist = list;
diff --git a/gtk2_ardour/automation_line.h b/gtk2_ardour/automation_line.h
index 1123a7b..7fd9dd3 100644
--- a/gtk2_ardour/automation_line.h
+++ b/gtk2_ardour/automation_line.h
@@ -162,7 +162,6 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulThingWithGoin
 	void sync_model_with_view_line (uint32_t, uint32_t);
 	
 	virtual void change_model (ARDOUR::AutomationList::iterator, double x, double y);
-	virtual void change_model_range (ARDOUR::AutomationList::iterator,ARDOUR::AutomationList::iterator, double delta, float ydelta);
 
 	void reset_callback (const Evoral::ControlList&);
 	void list_changed ();
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index 279de99..f56b163 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -355,6 +355,7 @@ class Editor : public PublicEditor
 	void update_layering_model ();
 	
 	void toggle_link_region_and_track_selection ();
+	void toggle_automation_follows_regions ();
 
 	/* redirect shared ops menu. caller must free returned menu */
 
diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc
index 0f78b60..2c46409 100644
--- a/gtk2_ardour/editor_actions.cc
+++ b/gtk2_ardour/editor_actions.cc
@@ -98,6 +98,7 @@ Editor::register_actions ()
 	/* add named actions for the editor */
 
 	ActionManager::register_toggle_action (editor_actions, "link-region-and-track-selection", _("Link Region/Track Selection"), mem_fun (*this, &Editor::toggle_link_region_and_track_selection));
+	ActionManager::register_toggle_action (editor_actions, "automation-follows-regions", _("Automation follows regions"), mem_fun (*this, &Editor::toggle_automation_follows_regions));
 	ActionManager::register_action (editor_actions, "break-drag", _("Break drag"), mem_fun (*this, &Editor::break_drag));
 
 	act = ActionManager::register_toggle_action (editor_actions, "show-editor-mixer", _("Show Editor Mixer"), mem_fun (*this, &Editor::editor_mixer_button_toggled));
@@ -1703,6 +1704,12 @@ Editor::toggle_link_region_and_track_selection ()
 	ActionManager::toggle_config_state ("Editor", "link-region-and-track-selection", &Configuration::set_link_region_and_track_selection, &Configuration::get_link_region_and_track_selection);
 }
 
+void
+Editor::toggle_automation_follows_regions ()
+{
+	ActionManager::toggle_config_state ("Editor", "automation-follows-regions", &Configuration::set_automation_follows_regions, &Configuration::get_automation_follows_regions);
+}
+
 /** A Configuration parameter has changed.
  * @param parameter_name Name of the changed parameter.
  */
@@ -1749,6 +1756,8 @@ Editor::parameter_changed (const char* parameter_name)
 		toggle_meter_updating();
 	} else if (PARAM_IS ("link-region-and-track-selection")) {
 		ActionManager::map_some_state ("Editor", "link-region-and-track-selection", &Configuration::get_link_region_and_track_selection);
+	} else if (PARAM_IS ("automation-follows-regions")) {
+		ActionManager::map_some_state ("Editor", "automation-follows-regions", &Configuration::get_automation_follows_regions);
 	}
 
 #undef PARAM_IS
diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc
index 373babf..fa0197c 100644
--- a/gtk2_ardour/mixer_strip.cc
+++ b/gtk2_ardour/mixer_strip.cc
@@ -1219,12 +1219,17 @@ MixerStrip::map_frozen ()
 void
 MixerStrip::hide_redirect_editors ()
 {
-	_route->foreach_processor (this, &MixerStrip::hide_processor_editor);
+	_route->foreach_processor (mem_fun (*this, &MixerStrip::hide_processor_editor));
 }
 
 void
-MixerStrip::hide_processor_editor (boost::shared_ptr<Processor> processor)
+MixerStrip::hide_processor_editor (boost::weak_ptr<Processor> p)
 {
+	boost::shared_ptr<Processor> processor (p.lock ());
+	if (!processor) {
+		return;
+	}
+	
 	void* gui = processor->get_gui ();
 	
 	if (gui) {
diff --git a/gtk2_ardour/mixer_strip.h b/gtk2_ardour/mixer_strip.h
index 63a4797..51b891d 100644
--- a/gtk2_ardour/mixer_strip.h
+++ b/gtk2_ardour/mixer_strip.h
@@ -239,7 +239,7 @@ class MixerStrip : public RouteUI, public Gtk::EventBox
 	void name_changed ();
 	void update_speed_display ();
 	void map_frozen ();
-	void hide_processor_editor (boost::shared_ptr<ARDOUR::Processor> processor);
+	void hide_processor_editor (boost::weak_ptr<ARDOUR::Processor> processor);
 	void hide_redirect_editors ();
 
 	bool ignore_speed_adjustment;
diff --git a/gtk2_ardour/processor_box.cc b/gtk2_ardour/processor_box.cc
index 370bb75..974cf5e 100644
--- a/gtk2_ardour/processor_box.cc
+++ b/gtk2_ardour/processor_box.cc
@@ -559,8 +559,7 @@ ProcessorBox::redisplay_processors ()
 	processor_active_connections.clear ();
 	processor_name_connections.clear ();
 
-	void (ProcessorBox::*method)(boost::shared_ptr<Processor>) = &ProcessorBox::add_processor_to_display;
-	_route->foreach_processor (this, method);
+	_route->foreach_processor (mem_fun (*this, &ProcessorBox::add_processor_to_display));
 
 	switch (_placement) {
 	case PreFader:
@@ -573,8 +572,13 @@ ProcessorBox::redisplay_processors ()
 }
 
 void
-ProcessorBox::add_processor_to_display (boost::shared_ptr<Processor> processor)
+ProcessorBox::add_processor_to_display (boost::weak_ptr<Processor> p)
 {
+	boost::shared_ptr<Processor> processor (p.lock ());
+	if (!processor) {
+		return;
+	}
+	
 	if (processor->placement() != _placement) {
 		return;
 	}
diff --git a/gtk2_ardour/processor_box.h b/gtk2_ardour/processor_box.h
index 5d3eb73..e148f14 100644
--- a/gtk2_ardour/processor_box.h
+++ b/gtk2_ardour/processor_box.h
@@ -156,7 +156,7 @@ class ProcessorBox : public Gtk::HBox, public PluginInterestedObject
 	bool processor_button_press_event (GdkEventButton *);
 	bool processor_button_release_event (GdkEventButton *);
 	void redisplay_processors ();
-	void add_processor_to_display (boost::shared_ptr<ARDOUR::Processor>);
+	void add_processor_to_display (boost::weak_ptr<ARDOUR::Processor>);
 	void row_deleted (const Gtk::TreeModel::Path& path);
 	void show_processor_active (boost::weak_ptr<ARDOUR::Processor>);
 	void show_processor_name (boost::weak_ptr<ARDOUR::Processor>);
diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc
index 71ad6e7..2a50b10 100644
--- a/gtk2_ardour/route_time_axis.cc
+++ b/gtk2_ardour/route_time_axis.cc
@@ -279,8 +279,8 @@ RouteTimeAxisView::post_construct ()
 	update_diskstream_display ();
 
 	subplugin_menu.items().clear ();
-	_route->foreach_processor (this, &RouteTimeAxisView::add_processor_to_subplugin_menu);
-	_route->foreach_processor (this, &RouteTimeAxisView::add_existing_processor_automation_curves);
+	_route->foreach_processor (mem_fun (*this, &RouteTimeAxisView::add_processor_to_subplugin_menu));
+	_route->foreach_processor (mem_fun (*this, &RouteTimeAxisView::add_existing_processor_automation_curves));
 	reset_processor_automation_curves ();
 }
 
@@ -1900,8 +1900,13 @@ RouteTimeAxisView::processor_automation_track_hidden (RouteTimeAxisView::Process
 }
 
 void
-RouteTimeAxisView::add_existing_processor_automation_curves (boost::shared_ptr<Processor> processor)
+RouteTimeAxisView::add_existing_processor_automation_curves (boost::weak_ptr<Processor> p)
 {
+	boost::shared_ptr<Processor> processor (p.lock ());
+	if (!processor) {
+		return;
+	}
+	
 	set<Evoral::Parameter> s;
 	boost::shared_ptr<AutomationLine> al;
 
@@ -1957,8 +1962,13 @@ RouteTimeAxisView::add_automation_child(Evoral::Parameter param, boost::shared_p
 
 
 void
-RouteTimeAxisView::add_processor_to_subplugin_menu (boost::shared_ptr<Processor> processor)
+RouteTimeAxisView::add_processor_to_subplugin_menu (boost::weak_ptr<Processor> p)
 {
+	boost::shared_ptr<Processor> processor (p.lock ());
+	if (!processor) {
+		return;
+	}
+	
 	using namespace Menu_Helpers;
 	ProcessorAutomationInfo *rai;
 	list<ProcessorAutomationInfo*>::iterator x;
@@ -2087,8 +2097,8 @@ RouteTimeAxisView::processors_changed ()
 
 	subplugin_menu.items().clear ();
 
-	_route->foreach_processor (this, &RouteTimeAxisView::add_processor_to_subplugin_menu);
-	_route->foreach_processor (this, &RouteTimeAxisView::add_existing_processor_automation_curves);
+	_route->foreach_processor (mem_fun (*this, &RouteTimeAxisView::add_processor_to_subplugin_menu));
+	_route->foreach_processor (mem_fun (*this, &RouteTimeAxisView::add_existing_processor_automation_curves));
 
 	for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ) {
 
diff --git a/gtk2_ardour/route_time_axis.h b/gtk2_ardour/route_time_axis.h
index 405f493..c41e60a 100644
--- a/gtk2_ardour/route_time_axis.h
+++ b/gtk2_ardour/route_time_axis.h
@@ -184,7 +184,7 @@ protected:
 
 	void processors_changed ();
 	
-	void add_processor_to_subplugin_menu (boost::shared_ptr<ARDOUR::Processor>);
+	void add_processor_to_subplugin_menu (boost::weak_ptr<ARDOUR::Processor>);
 	void remove_processor_automation_node (ProcessorAutomationNode* pan);
 
 	void processor_menu_item_toggled (RouteTimeAxisView::ProcessorAutomationInfo*,
@@ -205,7 +205,7 @@ protected:
 	find_processor_automation_curve (boost::shared_ptr<ARDOUR::Processor> i, Evoral::Parameter);
 
 	void add_processor_automation_curve (boost::shared_ptr<ARDOUR::Processor> r, Evoral::Parameter);
-	void add_existing_processor_automation_curves (boost::shared_ptr<ARDOUR::Processor>);
+	void add_existing_processor_automation_curves (boost::weak_ptr<ARDOUR::Processor>);
 
 	void add_automation_child(Evoral::Parameter param, boost::shared_ptr<AutomationTimeAxisView> track, bool show=true);
 	
diff --git a/libs/ardour/ardour/configuration_vars.h b/libs/ardour/ardour/configuration_vars.h
index 5565e20..ce1dc89 100644
--- a/libs/ardour/ardour/configuration_vars.h
+++ b/libs/ardour/ardour/configuration_vars.h
@@ -82,6 +82,7 @@ CONFIG_VARIABLE (EditMode, edit_mode, "edit-mode", Slide)
 CONFIG_VARIABLE (LayerModel, layer_model, "layer-model", MoveAddHigher)
 CONFIG_VARIABLE (bool, link_region_and_track_selection, "link-region-and-track-selection", false)
 CONFIG_VARIABLE (std::string, keyboard_layout_name, "keyboard-layout-name", "ansi")
+CONFIG_VARIABLE (bool, automation_follows_regions, "automation-follows-regions", false)
 
 /* monitoring, mute, solo etc */
 
diff --git a/libs/ardour/ardour/diskstream.h b/libs/ardour/ardour/diskstream.h
index 824d864..955f2ed 100644
--- a/libs/ardour/ardour/diskstream.h
+++ b/libs/ardour/ardour/diskstream.h
@@ -140,6 +140,8 @@ class Diskstream : public SessionObject
 
 	void remove_region_from_last_capture (boost::weak_ptr<Region> wregion);
 
+	void move_processor_automation (boost::weak_ptr<Processor>, Evoral::RangeMoveList const &);
+
 	sigc::signal<void>            RecordEnableChanged;
 	sigc::signal<void>            SpeedChanged;
 	sigc::signal<void>            ReverseChanged;
@@ -204,6 +206,7 @@ class Diskstream : public SessionObject
 
 	virtual void playlist_changed (Change);
 	virtual void playlist_deleted (boost::weak_ptr<Playlist>);
+	virtual void playlist_ranges_moved (Evoral::RangeMoveList const &);
 
 	virtual void transport_stopped (struct tm&, time_t, bool abort) = 0;
 	virtual void transport_looped (nframes_t transport_frame) = 0;
@@ -299,6 +302,7 @@ class Diskstream : public SessionObject
 	sigc::connection ports_created_c;
 	sigc::connection plmod_connection;
 	sigc::connection plgone_connection;
+	sigc::connection plregion_connection;
 	
 	Flag _flags;
 };
diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h
index b937c41..cdde985 100644
--- a/libs/ardour/ardour/playlist.h
+++ b/libs/ardour/ardour/playlist.h
@@ -35,7 +35,9 @@
 
 #include <pbd/undo.h>
 #include <pbd/stateful.h> 
-#include <pbd/statefuldestructible.h> 
+#include <pbd/statefuldestructible.h>
+
+#include <evoral/types.hpp>
 
 #include <ardour/ardour.h>
 #include <ardour/session_object.h>
@@ -126,6 +128,7 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla
 	sigc::signal<void>      Modified;
 	sigc::signal<void>      NameChanged;
 	sigc::signal<void>      LengthChanged;
+	sigc::signal<void, Evoral::RangeMoveList const &> RangesMoved;
 
 	static string bump_name (string old_name, Session&);
 
@@ -177,6 +180,7 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla
 
 	RegionList       regions;  /* the current list of regions in the playlist */
 	std::set<boost::shared_ptr<Region> > all_regions; /* all regions ever added to this playlist */
+	std::list<sigc::connection> region_state_changed_connections;
 	DataType        _type;
 	mutable gint    block_notifications;
 	mutable gint    ignore_state_changes;
@@ -186,6 +190,7 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla
 	RegionList       pending_bounds;
 	bool             pending_modified;
 	bool             pending_length;
+	Evoral::RangeMoveList pending_range_moves;
 	bool             save_on_thaw;
 	string           last_save_reason;
 	uint32_t         in_set_state;
@@ -227,6 +232,7 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla
 	void notify_layering_changed ();
 	void notify_modified ();
 	void notify_state_changed (Change);
+	void notify_region_moved (boost::shared_ptr<Region>);
 
 	void mark_session_dirty();
 
diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h
index fb98cb5..59d4637 100644
--- a/libs/ardour/ardour/route.h
+++ b/libs/ardour/ardour/route.h
@@ -140,10 +140,10 @@ class Route : public IO
 
 	void flush_processors ();
 
-	template<class T> void foreach_processor (T *obj, void (T::*func)(boost::shared_ptr<Processor>)) {
+	void foreach_processor (sigc::slot<void, boost::weak_ptr<Processor> > method) {
 		Glib::RWLock::ReaderLock lm (_processor_lock);
 		for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
-			(obj->*func) (*i);
+			method (boost::weak_ptr<Processor> (*i));
 		}
 	}
 
diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc
index 9665176..334e433 100644
--- a/libs/ardour/diskstream.cc
+++ b/libs/ardour/diskstream.cc
@@ -37,6 +37,7 @@
 #include <pbd/basename.h>
 #include <glibmm/thread.h>
 #include <pbd/xml++.h>
+#include <pbd/memento_command.h>
 
 #include <ardour/ardour.h>
 #include <ardour/audioengine.h>
@@ -48,6 +49,7 @@
 #include <ardour/playlist.h>
 #include <ardour/cycle_timer.h>
 #include <ardour/region.h>
+#include <ardour/panner.h>
 
 #include "i18n.h"
 #include <locale.h>
@@ -312,6 +314,7 @@ Diskstream::use_playlist (boost::shared_ptr<Playlist> playlist)
 
 		plmod_connection.disconnect ();
 		plgone_connection.disconnect ();
+		plregion_connection.disconnect ();
 
 		if (_playlist) {
 			_playlist->release();
@@ -326,6 +329,7 @@ Diskstream::use_playlist (boost::shared_ptr<Playlist> playlist)
 		
 		plmod_connection = _playlist->Modified.connect (mem_fun (*this, &Diskstream::playlist_modified));
 		plgone_connection = _playlist->GoingAway.connect (bind (mem_fun (*this, &Diskstream::playlist_deleted), boost::weak_ptr<Playlist>(_playlist)));
+		plregion_connection = _playlist->RangesMoved.connect (mem_fun (*this, &Diskstream::playlist_ranges_moved));
 	}
 
 	/* don't do this if we've already asked for it *or* if we are setting up
@@ -409,3 +413,64 @@ Diskstream::remove_region_from_last_capture (boost::weak_ptr<Region> wregion)
 	_last_capture_regions.remove (region);
 }
 
+void
+Diskstream::playlist_ranges_moved (Evoral::RangeMoveList const & movements)
+{
+	if (Config->get_automation_follows_regions () == false) {
+		return;
+	}
+	
+	/* move gain automation */
+	boost::shared_ptr<AutomationList> gain_alist = _io->gain_control()->alist();
+	XMLNode & before = gain_alist->get_state ();
+	gain_alist->move_ranges (movements);
+	_session.add_command (
+		new MementoCommand<AutomationList> (
+			*gain_alist.get(), &before, &gain_alist->get_state ()
+			)
+		);
+	
+	/* move panner automation */
+	Panner & p = _io->panner ();
+	for (uint32_t i = 0; i < p.npanners (); ++i) {
+
+		boost::shared_ptr<AutomationList> pan_alist = p.streampanner(i).pan_control()->alist();
+		XMLNode & before = pan_alist->get_state ();
+		pan_alist->move_ranges (movements);
+		_session.add_command (
+			new MementoCommand<AutomationList> (
+				*pan_alist.get(), &before, &pan_alist->get_state ()
+				)
+			);
+	}
+
+	/* move processor automation */
+	/* XXX: ewww */
+	Route * route = dynamic_cast<Route*> (_io);
+	if (route) {
+		route->foreach_processor (sigc::bind (sigc::mem_fun (*this, &Diskstream::move_processor_automation), movements));
+	}
+}
+
+void
+Diskstream::move_processor_automation (boost::weak_ptr<Processor> p, Evoral::RangeMoveList const & movements)
+{
+	boost::shared_ptr<Processor> processor (p.lock ());
+	if (!processor) {
+		return;
+	}
+	
+	set<Evoral::Parameter> const a = processor->what_can_be_automated ();
+
+	for (set<Evoral::Parameter>::iterator i = a.begin (); i != a.end (); ++i) {
+		boost::shared_ptr<AutomationList> al = processor->automation_control(*i)->alist();
+		XMLNode & before = al->get_state ();
+		al->move_ranges (movements);
+		_session.add_command (
+			new MementoCommand<AutomationList> (
+				*al.get(), &before, &al->get_state ()
+				)
+			);
+	}
+}
+
diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc
index fbe9990..433e567 100644
--- a/libs/ardour/playlist.cc
+++ b/libs/ardour/playlist.cc
@@ -361,6 +361,25 @@ Playlist::notify_region_removed (boost::shared_ptr<Region> r)
 }
 
 void
+Playlist::notify_region_moved (boost::shared_ptr<Region> r)
+{
+	Evoral::RangeMove const move (r->last_position (), r->length (), r->position ());
+			
+	if (holding_state ()) {
+
+		pending_range_moves.push_back (move);
+		
+	} else {
+
+		Evoral::RangeMoveList m;
+		m.push_back (move);
+		RangesMoved (m);
+
+	}
+
+}
+
+void
 Playlist::notify_region_added (boost::shared_ptr<Region> r)
 {
 	/* the length change might not be true, but we have to act
@@ -452,9 +471,14 @@ Playlist::flush_notifications ()
 		check_dependents (*s, false);
 	}
 
+	if (!pending_range_moves.empty ()) {
+		RangesMoved (pending_range_moves);
+	}
+
 	pending_adds.clear ();
 	pending_removes.clear ();
 	pending_bounds.clear ();
+	pending_range_moves.clear ();
 
 	in_flush = false;
 }
@@ -559,8 +583,10 @@ Playlist::add_region_internal (boost::shared_ptr<Region> region, nframes_t posit
 		}
 	}
 
-	region->StateChanged.connect (sigc::bind (mem_fun (this, &Playlist::region_changed_proxy), 
-						  boost::weak_ptr<Region> (region)));
+	region_state_changed_connections.push_back (
+		region->StateChanged.connect (sigc::bind (mem_fun (this, &Playlist::region_changed_proxy), 
+							  boost::weak_ptr<Region> (region)))
+		);
 
 	return true;
 }
@@ -1288,20 +1314,22 @@ Playlist::region_changed (Change what_changed, boost::shared_ptr<Region> region)
 		return false;
 	}
 
-	{
-		if (what_changed & BoundsChanged) {
-			region_bounds_changed (what_changed, region);
-			save = !(_splicing || _nudging);
-		}
+	if (what_changed & BoundsChanged) {
+		region_bounds_changed (what_changed, region);
+		save = !(_splicing || _nudging);
+	}
 		
-		if ((what_changed & our_interests) && 
-		    !(what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged))) {
-			check_dependents (region, false);
-		}
+	if ((what_changed & our_interests) && 
+	    !(what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged))) {
+		check_dependents (region, false);
+	}
+
+	if (what_changed & Change (ARDOUR::PositionChanged)) {
+		notify_region_moved (region);
+	}
 		
-		if (what_changed & our_interests) {
-			save = true;
-		}
+	if (what_changed & our_interests) {
+		save = true;
 	}
 
 	return save;
@@ -1320,6 +1348,17 @@ Playlist::clear (bool with_signals)
 {
 	{ 
 		RegionLock rl (this);
+
+		for (
+			std::list<sigc::connection>::iterator i = region_state_changed_connections.begin ();
+			i != region_state_changed_connections.end ();
+			++i
+			) {
+
+			i->disconnect ();
+			
+		}
+		     
 		for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
 			pending_removes.insert (*i);
 		}
diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp
index 3679930..48050f9 100644
--- a/libs/evoral/evoral/ControlList.hpp
+++ b/libs/evoral/evoral/ControlList.hpp
@@ -120,7 +120,7 @@ public:
 	void erase_range (double start, double end);
 	void erase (iterator);
 	void erase (iterator, iterator);
-	void move_range (iterator start, iterator end, double, double);
+	void move_ranges (RangeMoveList const &);
 	void modify (iterator, double, double);
 
 	boost::shared_ptr<ControlList> cut (double, double);
@@ -241,6 +241,7 @@ protected:
 	bool rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const;
 
 	boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
+	bool erase_range_internal (double start, double end, EventList &);
 
 	virtual void maybe_signal_changed ();
 	
diff --git a/libs/evoral/evoral/types.hpp b/libs/evoral/evoral/types.hpp
index fc1001c..4bca6f1 100644
--- a/libs/evoral/evoral/types.hpp
+++ b/libs/evoral/evoral/types.hpp
@@ -20,6 +20,7 @@
 #define EVORAL_TYPES_HPP
 
 #include <stdint.h>
+#include <list>
 
 namespace Evoral {
 
@@ -41,6 +42,16 @@ typedef double EventLength;
 /** Type of an event (opaque, mapped by application) */
 typedef uint32_t EventType;
 
+/** Type to describe the movement of a time range */	
+struct RangeMove {
+	RangeMove (EventTime f, FrameTime l, EventTime t) : from (f), length (l), to (t) {}
+	EventTime from;   ///< start of the range
+	FrameTime length; ///< length of the range
+	EventTime to;     ///< new start of the range
+};
+
+typedef std::list<RangeMove> RangeMoveList;
+
 } // namespace Evoral
 
 #endif // EVORAL_TYPES_HPP
diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp
index e1cea8e..033e375 100644
--- a/libs/evoral/src/ControlList.cpp
+++ b/libs/evoral/src/ControlList.cpp
@@ -390,16 +390,10 @@ ControlList::erase_range (double start, double endt)
 
 	{
 		Glib::Mutex::Lock lm (_lock);
-		ControlEvent cp (start, 0.0f);
-		iterator s;
-		iterator e;
+		erased = erase_range_internal (start, endt, _events);
 
-		if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
-			cp.when = endt;
-			e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
-			_events.erase (s, e);
+		if (erased) {
 			reposition_for_rt_add (0);
-			erased = true;
 			mark_dirty ();
 		}
 		
@@ -410,36 +404,22 @@ ControlList::erase_range (double start, double endt)
 	}
 }
 
-void
-ControlList::move_range (iterator start, iterator end, double xdelta, double ydelta)
+bool
+ControlList::erase_range_internal (double start, double endt, EventList & events)
 {
-	/* note: we assume higher level logic is in place to avoid this
-	   reordering the time-order of control events in the list. ie. all
-	   points after end are later than (end)->when.
-	*/
-
-	{
-		Glib::Mutex::Lock lm (_lock);
-
-		while (start != end) {
-			(*start)->when += xdelta;
-			(*start)->value += ydelta;
-			if (isnan ((*start)->value)) {
-				abort ();
-			}
-			++start;
-		}
-
-		if (!_frozen) {
-			_events.sort (event_time_less_than);
-		} else {
-			_sort_pending = true;
-		}
-
-		mark_dirty ();
+	bool erased = false;
+	ControlEvent cp (start, 0.0f);
+	iterator s;
+	iterator e;
+
+	if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
+		cp.when = endt;
+		e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
+		events.erase (s, e);
+		erased = true;
 	}
 
-	maybe_signal_changed ();
+	return erased;
 }
 
 void
@@ -1315,5 +1295,51 @@ ControlList::paste (ControlList& alist, double pos, float times)
 	return true;
 }
 
+/** Move automation around according to a list of region movements */
+void
+ControlList::move_ranges (RangeMoveList const & movements)
+{
+	{
+		Glib::Mutex::Lock lm (_lock);
+
+		/* a copy of the events list before we started moving stuff around */
+		EventList old_events = _events;
+
+		/* clear the source and destination ranges in the new list */
+		for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+
+			erase_range_internal (i->from, i->from + i->length, _events);
+			erase_range_internal (i->to, i->to + i->length, _events);
+
+		}
+
+		/* copy the events into the new list */
+		for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+			iterator j = old_events.begin ();
+			EventTime const limit = i->from + i->length;
+			EventTime const dx = i->to - i->from;
+			while (j != old_events.end () && (*j)->when <= limit) {
+				if ((*j)->when >= i->from) {
+					ControlEvent* ev = new ControlEvent (**j);
+					ev->when += dx;
+					_events.push_back (ev);
+				}
+				++j;
+			}
+		}
+
+		if (!_frozen) {
+			_events.sort (event_time_less_than);
+		} else {
+			_sort_pending = true;
+		}
+
+		reposition_for_rt_add (0);
+		mark_dirty ();
+	}
+
+	maybe_signal_changed ();
+}
+
 } // namespace Evoral
 
diff --git a/libs/evoral/test/SConstruct b/libs/evoral/test/SConstruct
new file mode 100644
index 0000000..538c68e
--- /dev/null
+++ b/libs/evoral/test/SConstruct
@@ -0,0 +1,18 @@
+
+import os
+
+env = Environment ()
+
+tests = 'control_list.hpp'
+
+env.Append (CPPPATH = ['../../glibmm2/glib', '../../sigc++2', '..'])
+env.Append (LIBPATH = ['..', '../../glibmm2', '../../sigc++2', '../../pbd'])
+env.Append (LIBS = ['evoral', 'glibmm2', 'sigc++2', 'pbd'])
+env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
+env.Command ('runner.cpp', Split (tests), 'cxxtestgen.py --error-printer -o $TARGET $SOURCES')
+runner = env.Program ('runner', 'runner.cpp')
+e = os.environ
+e['LD_LIBRARY_PATH'] = '..:../../glibmm2:../../sigc++2:../../pbd'
+env.Append (ENV = e)
+env.Command ('test', runner, './runner')
+
diff --git a/libs/evoral/test/control_list.hpp b/libs/evoral/test/control_list.hpp
new file mode 100644
index 0000000..d582cdc
--- /dev/null
+++ b/libs/evoral/test/control_list.hpp
@@ -0,0 +1,71 @@
+#include <glibmm.h>
+#include <cxxtest/TestSuite.h>
+#include "evoral/ControlList.hpp"
+
+class ControlListTests : public CxxTest::TestSuite {
+
+public:
+	void test_move_ranges_one_region ()
+	{
+		Glib::thread_init ();
+		
+		Evoral::ControlList cl (Evoral::Parameter (0));
+
+		cl.add (0, 0);
+		cl.add (1, 1);
+		cl.add (2, 2);
+		cl.add (3, 3);
+
+		Evoral::RangeMoveList ml;
+
+		ml.push_back (Evoral::RangeMove (0, 2, 4));
+
+		cl.move_ranges (ml);
+
+		Evoral::ControlList::iterator i = cl.begin ();
+		TS_ASSERT_EQUALS ((*i)->when, 3);
+		TS_ASSERT_EQUALS ((*i)->value, 3);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 4);
+		TS_ASSERT_EQUALS ((*i)->value, 0);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 5);
+		TS_ASSERT_EQUALS ((*i)->value, 1);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 6);
+		TS_ASSERT_EQUALS ((*i)->value, 2);
+	}
+
+	void test_move_ranges_two_regions ()
+	{
+		Evoral::ControlList cl (Evoral::Parameter (0));
+
+		cl.add (0, 0);
+		cl.add (1, 1);
+		cl.add (2, 2);
+		cl.add (3, 3);
+
+		Evoral::RangeMoveList ml;
+
+		ml.push_back (Evoral::RangeMove (0, 1, 2));
+		ml.push_back (Evoral::RangeMove (2, 3, 4));
+
+		cl.move_ranges (ml);
+
+		Evoral::ControlList::iterator i = cl.begin ();
+		TS_ASSERT_EQUALS ((*i)->when, 2);
+		TS_ASSERT_EQUALS ((*i)->value, 0);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 3);
+		TS_ASSERT_EQUALS ((*i)->value, 1);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 4);
+		TS_ASSERT_EQUALS ((*i)->value, 2);
+		++i;
+		TS_ASSERT_EQUALS ((*i)->when, 5);
+		TS_ASSERT_EQUALS ((*i)->value, 3);
+		
+
+	}
+
+};
