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.
This commit is contained in:
Colin Fletcher 2023-06-08 22:33:14 +01:00
parent 96eb7652c9
commit 72850d456f
6 changed files with 56 additions and 24 deletions

View File

@ -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 ();

View File

@ -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;
}
}

View File

@ -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 */

View File

@ -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

View File

@ -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)
@ -64,6 +64,7 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim
, stretch_opts_label (_("Contents"))
, precise_button (_("Minimize time distortion"))
, preserve_formants_button(_("Preserve Formants"))
, fixed_end (fixed_end)
, original_length (oldlen)
, pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0)
, pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0)

View File

@ -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;