From 72850d456f218abc8204614383a2b03b15c9d817 Mon Sep 17 00:00:00 2001 From: Colin Fletcher 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 > ResultMap; ResultMap results; @@ -426,7 +432,11 @@ Editor::do_timefx () std::shared_ptr 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..899a69b440 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) @@ -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) 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;