View Issue Details

IDProjectCategoryView StatusLast Update
0009365ardourfeaturespublic2023-06-09 18:26
Reportercolinf Assigned Tox42  
PrioritynormalSeverityminorReproducibilityN/A
Status closedResolutionfixed 
Summary0009365: Allow time stretch to extend regions to the left
DescriptionUser edrickblade4 requests on the forum at https://discourse.ardour.org/t/audio-warp-in-ardour/108821/19 that time stretch could be made to stretch regions towards the left as well as to the right.
Additional InformationHere's a (very lightly tested) patch I hacked together to do just that - feedback welcome!
TagsNo tags attached.

Activities

colinf

2023-06-09 15:07

updater  

0001-implement-time-stretch-from-left-of-region.patch (11,146 bytes)   
From 793379bd53366bcfbab1506dd36ae2c47288e76e Mon Sep 17 00:00:00 2001
From: Colin Fletcher <colin.m.fletcher@googlemail.com>
Date: Thu, 8 Jun 2023 22:33:14 +0100
Subject: [PATCH] implement time-stretch from left of region

Make click & drag in the left-hand half of a region with the Timestretch
tool stretch the region on its left, leaving the end position of the new
time-stretched region in the same place as the end of the original.
---
 gtk2_ardour/editor.h          |  6 +++---
 gtk2_ardour/editor_drag.cc    | 36 ++++++++++++++++++++++++++---------
 gtk2_ardour/editor_drag.h     |  2 ++
 gtk2_ardour/editor_timefx.cc  | 30 +++++++++++++++++++----------
 gtk2_ardour/time_fx_dialog.cc |  3 ++-
 gtk2_ardour/time_fx_dialog.h  |  3 ++-
 6 files changed, 56 insertions(+), 24 deletions(-)

diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index 8cba3635b9..0e02fefd41 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -2251,9 +2251,9 @@ private:
 
 	TimeFXDialog* current_timefx;
 	static void* timefx_thread (void* arg);
-	void do_timefx ();
+	void do_timefx (bool fixed_end);
 
-	int time_stretch (RegionSelection&, Temporal::ratio_t const & fraction);
+	int time_stretch (RegionSelection&, Temporal::ratio_t const & fraction, bool fixed_end);
 	int pitch_shift (RegionSelection&, float cents);
 	void pitch_shift_region ();
 
@@ -2486,7 +2486,7 @@ private:
 	void set_show_touched_automation (bool);
 	bool _show_touched_automation;
 
-	int time_fx (ARDOUR::RegionList&, Temporal::ratio_t ratio, bool pitching);
+	int time_fx (ARDOUR::RegionList&, Temporal::ratio_t ratio, bool pitching, bool fixed_end);
 	void note_edit_done (int, EditNoteDialog*);
 	void toggle_sound_midi_notes ();
 
diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc
index 9ad26b58a5..4f19506275 100644
--- a/gtk2_ardour/editor_drag.cc
+++ b/gtk2_ardour/editor_drag.cc
@@ -5302,7 +5302,9 @@ TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 	timepos_t where (_primary->region ()->position ());
 	setup_snap_delta (_primary->region ()->position ());
 
-	show_verbose_cursor_duration (where, adjusted_current_time (event), 0);
+	timepos_t clicked_pos = adjusted_current_time (event);
+	show_verbose_cursor_duration (where, clicked_pos, 0);
+	_dragging_start = clicked_pos < _primary->region ()->position () + _primary->region ()->length ().scale(ratio_t(1, 2));
 }
 
 void
@@ -5319,8 +5321,15 @@ TimeFXDrag::motion (GdkEvent* event, bool)
 	_editor->snap_to_with_modifier (pf, event);
 	pf.shift_earlier (snap_delta (event->button.state));
 
-	if (pf > rv->region ()->position ()) {
-		rv->get_time_axis_view ().show_timestretch (rv->region ()->position (), pf, layers, layer);
+	if (_dragging_start) {
+		if (pf < rv->region ()->end ()) {
+			rv->get_time_axis_view ().show_timestretch (pf, rv->region ()->end (), layers, layer);
+
+		}
+	} else {
+		if (pf > rv->region ()->position ()) {
+			rv->get_time_axis_view ().show_timestretch (rv->region ()->position (), pf, layers, layer);
+		}
 	}
 
 	show_verbose_cursor_duration (_primary->region ()->position (), pf);
@@ -5342,7 +5351,7 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
 	if (!movement_occurred) {
 		_primary->get_time_axis_view ().hide_timestretch ();
 
-		if (_editor->time_stretch (_editor->get_selection ().regions, ratio_t (1, 1)) == -1) {
+		if (_editor->time_stretch (_editor->get_selection ().regions, ratio_t (1, 1), false) == -1) {
 			error << _("An error occurred while executing time stretch operation") << endmsg;
 		}
 		return;
@@ -5355,13 +5364,22 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
 	_primary->get_time_axis_view ().hide_timestretch ();
 
 	timepos_t adjusted_pos = adjusted_current_time (event);
+	timecnt_t newlen;
 
-	if (adjusted_pos < _primary->region ()->position ()) {
-		/* backwards drag of the left edge - not usable */
-		return;
+	if (_dragging_start) {
+		if (adjusted_pos > _primary->region ()->end ()) {
+			/* forwards drag of the right edge - not usable */
+			return;
+		}
+		newlen = _primary->region ()->end ().distance (adjusted_pos);
+	} else {
+		if (adjusted_pos < _primary->region ()->position ()) {
+			/* backwards drag of the left edge - not usable */
+			return;
+		}
+		newlen = _primary->region ()->position ().distance (adjusted_pos);
 	}
 
-	timecnt_t newlen = _primary->region ()->position ().distance (adjusted_pos);
 
 	if (_primary->region ()->length ().time_domain () == Temporal::BeatTime) {
 		ratio = ratio_t (newlen.ticks (), _primary->region ()->length ().ticks ());
@@ -5383,7 +5401,7 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
 	   selection.
 	*/
 
-	if (_editor->time_stretch (_editor->get_selection ().regions, ratio) == -1) {
+	if (_editor->time_stretch (_editor->get_selection ().regions, ratio, _dragging_start) == -1) {
 		error << _("An error occurred while executing time stretch operation") << endmsg;
 	}
 }
diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h
index 4729886098..ce84b54427 100644
--- a/gtk2_ardour/editor_drag.h
+++ b/gtk2_ardour/editor_drag.h
@@ -1335,6 +1335,8 @@ public:
 	void motion (GdkEvent *, bool);
 	void finished (GdkEvent *, bool);
 	void aborted (bool);
+private:
+	bool _dragging_start;
 };
 
 /** Scrub drag in audition mode */
diff --git a/gtk2_ardour/editor_timefx.cc b/gtk2_ardour/editor_timefx.cc
index cd529725e9..c78328ace3 100644
--- a/gtk2_ardour/editor_timefx.cc
+++ b/gtk2_ardour/editor_timefx.cc
@@ -66,7 +66,7 @@ using namespace Gtkmm2ext;
 
 /** @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
 int
-Editor::time_stretch (RegionSelection& regions, Temporal::ratio_t const & ratio)
+Editor::time_stretch (RegionSelection& regions, Temporal::ratio_t const & ratio, bool fixed_end)
 {
 	RegionList audio;
 	RegionList midi;
@@ -81,7 +81,7 @@ Editor::time_stretch (RegionSelection& regions, Temporal::ratio_t const & ratio)
 		}
 	}
 
-	int aret = time_fx (audio, ratio, false);
+	int aret = time_fx (audio, ratio, false, fixed_end);
 	if (aret < 0) {
 		abort_reversible_command ();
 		return aret;
@@ -111,8 +111,14 @@ Editor::time_stretch (RegionSelection& regions, Temporal::ratio_t const & ratio)
 		MidiStretch stretch (*_session, request);
 		stretch.run (*i);
 
-		playlist->replace_region (regions.front()->region(), stretch.results[0],
-		                          regions.front()->region()->position());
+		timepos_t newpos;
+
+		if (fixed_end)
+			newpos = regions.front()->region()->end().earlier(stretch.results[0]->length());
+		else
+			newpos = regions.front()->region()->position();
+
+		playlist->replace_region (regions.front()->region(), stretch.results[0], newpos);
 		midi_playlists_affected.insert (playlist);
 	}
 
@@ -144,7 +150,7 @@ Editor::pitch_shift (RegionSelection& regions, float fraction)
 
 	begin_reversible_command (_("pitch shift"));
 
-	int ret = time_fx (rl, fraction, true);
+	int ret = time_fx (rl, fraction, true, false);
 
 	if (ret > 0) {
 		commit_reversible_command ();
@@ -159,7 +165,7 @@ Editor::pitch_shift (RegionSelection& regions, float fraction)
  *  @param pitching true to pitch shift, false to time stretch.
  *  @return -1 in case of error, otherwise number of regions processed */
 int
-Editor::time_fx (RegionList& regions, Temporal::ratio_t ratio, bool pitching)
+Editor::time_fx (RegionList& regions, Temporal::ratio_t ratio, bool pitching, bool fixed_end)
 {
 	delete current_timefx;
 
@@ -172,7 +178,7 @@ Editor::time_fx (RegionList& regions, Temporal::ratio_t ratio, bool pitching)
 	const timecnt_t newlen = regions.front()->length().scale (ratio);
 	const timepos_t pos = regions.front()->position ();
 
-	current_timefx = new TimeFXDialog (*this, pitching, oldlen, newlen, ratio, pos);
+	current_timefx = new TimeFXDialog (*this, pitching, oldlen, newlen, ratio, pos, fixed_end);
 	current_timefx->regions = regions;
 
 	switch (current_timefx->run ()) {
@@ -355,7 +361,7 @@ Editor::time_fx (RegionList& regions, Temporal::ratio_t ratio, bool pitching)
 }
 
 void
-Editor::do_timefx ()
+Editor::do_timefx (bool fixed_end)
 {
 	typedef std::map<std::shared_ptr<Region>, std::shared_ptr<Region> > ResultMap;
 	ResultMap results;
@@ -426,7 +432,11 @@ Editor::do_timefx ()
 			std::shared_ptr<Playlist> playlist = region->playlist();
 
 			playlist->clear_changes ();
-			playlist->replace_region (region, new_region, region->position());
+			if (fixed_end)
+				playlist->replace_region (region, new_region, region->end ().earlier(new_region->length ()));
+			else
+				playlist->replace_region (region, new_region, region->position());
+
 			PBD::StatefulDiffCommand* cmd = new StatefulDiffCommand (playlist);
 			_session->add_command (cmd);
 			if (!cmd->empty ()) {
@@ -449,7 +459,7 @@ Editor::timefx_thread (void *arg)
 
 	pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
 
-	tsd->editor.do_timefx ();
+	tsd->editor.do_timefx (tsd->fixed_end);
 
 	/* GACK! HACK! sleep for a bit so that our request buffer for the GUI
 	   event loop doesn't die before any changes we made are processed
diff --git a/gtk2_ardour/time_fx_dialog.cc b/gtk2_ardour/time_fx_dialog.cc
index 7c58bd2f5f..e3e702a3e9 100644
--- a/gtk2_ardour/time_fx_dialog.cc
+++ b/gtk2_ardour/time_fx_dialog.cc
@@ -55,7 +55,7 @@ using namespace PBD;
 using namespace Gtk;
 using namespace Gtkmm2ext;
 
-TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, timecnt_t const & new_length, Temporal::ratio_t const & ratio, timepos_t const & position)
+TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, timecnt_t const & new_length, Temporal::ratio_t const & ratio, timepos_t const & position, bool fixed_end)
 	: ArdourDialog (X_("time fx dialog"))
 	, editor (e)
 	, pitching (pitch)
@@ -82,6 +82,7 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim
 	set_skip_taskbar_hint (true);
 	set_resizable (false);
 	set_name (N_("TimeFXDialog"));
+	this->fixed_end = fixed_end;
 
 	if (pitching) {
 		set_title (_("Pitch Shift Audio"));
diff --git a/gtk2_ardour/time_fx_dialog.h b/gtk2_ardour/time_fx_dialog.h
index f4a80bd793..d1665e776d 100644
--- a/gtk2_ardour/time_fx_dialog.h
+++ b/gtk2_ardour/time_fx_dialog.h
@@ -42,7 +42,7 @@ class TimeFXDialog : public ArdourDialog, public ProgressReporter
 {
 public:
 	/* We need a position so that BBT mode in the clock can function */
-	TimeFXDialog (Editor& e, bool for_pitch, Temporal::timecnt_t const & old_length, Temporal::timecnt_t const & new_length, Temporal::ratio_t const &, Temporal::timepos_t const & position);
+	TimeFXDialog (Editor& e, bool for_pitch, Temporal::timecnt_t const & old_length, Temporal::timecnt_t const & new_length, Temporal::ratio_t const &, Temporal::timepos_t const & position, bool fixed_end);
 
 	ARDOUR::TimeFXRequest request;
 	Editor&               editor;
@@ -65,6 +65,7 @@ public:
 	Gtk::Button*          action_button;
 	Gtk::VBox             packer;
 	int                   status;
+	bool                  fixed_end;
 
 	sigc::connection first_cancel;
 	sigc::connection first_delete;
-- 
2.30.2

x42

2023-06-09 18:26

administrator   ~0027725

Applied as 7.4-240-g72850d456f

Issue History

Date Modified Username Field Change
2023-06-09 15:07 colinf New Issue
2023-06-09 15:07 colinf File Added: 0001-implement-time-stretch-from-left-of-region.patch
2023-06-09 18:26 x42 Assigned To => x42
2023-06-09 18:26 x42 Status new => closed
2023-06-09 18:26 x42 Resolution open => fixed
2023-06-09 18:26 x42 Note Added: 0027725