13
0

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; TimeFXDialog* current_timefx;
static void* timefx_thread (void* arg); 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); int pitch_shift (RegionSelection&, float cents);
void pitch_shift_region (); void pitch_shift_region ();
@ -2486,7 +2486,7 @@ private:
void set_show_touched_automation (bool); void set_show_touched_automation (bool);
bool _show_touched_automation; 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 note_edit_done (int, EditNoteDialog*);
void toggle_sound_midi_notes (); 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 ()); timepos_t where (_primary->region ()->position ());
setup_snap_delta (_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 void
@ -5319,9 +5321,16 @@ TimeFXDrag::motion (GdkEvent* event, bool)
_editor->snap_to_with_modifier (pf, event); _editor->snap_to_with_modifier (pf, event);
pf.shift_earlier (snap_delta (event->button.state)); pf.shift_earlier (snap_delta (event->button.state));
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 ()) { if (pf > rv->region ()->position ()) {
rv->get_time_axis_view ().show_timestretch (rv->region ()->position (), pf, layers, layer); rv->get_time_axis_view ().show_timestretch (rv->region ()->position (), pf, layers, layer);
} }
}
show_verbose_cursor_duration (_primary->region ()->position (), pf); show_verbose_cursor_duration (_primary->region ()->position (), pf);
} }
@ -5342,7 +5351,7 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
if (!movement_occurred) { if (!movement_occurred) {
_primary->get_time_axis_view ().hide_timestretch (); _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; error << _("An error occurred while executing time stretch operation") << endmsg;
} }
return; return;
@ -5355,13 +5364,22 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
_primary->get_time_axis_view ().hide_timestretch (); _primary->get_time_axis_view ().hide_timestretch ();
timepos_t adjusted_pos = adjusted_current_time (event); timepos_t adjusted_pos = adjusted_current_time (event);
timecnt_t newlen;
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 ()) { if (adjusted_pos < _primary->region ()->position ()) {
/* backwards drag of the left edge - not usable */ /* backwards drag of the left edge - not usable */
return; 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) { if (_primary->region ()->length ().time_domain () == Temporal::BeatTime) {
ratio = ratio_t (newlen.ticks (), _primary->region ()->length ().ticks ()); ratio = ratio_t (newlen.ticks (), _primary->region ()->length ().ticks ());
@ -5383,7 +5401,7 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
selection. 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; error << _("An error occurred while executing time stretch operation") << endmsg;
} }
} }

View File

@ -1335,6 +1335,8 @@ public:
void motion (GdkEvent *, bool); void motion (GdkEvent *, bool);
void finished (GdkEvent *, bool); void finished (GdkEvent *, bool);
void aborted (bool); void aborted (bool);
private:
bool _dragging_start;
}; };
/** Scrub drag in audition mode */ /** 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 */ /** @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
int 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 audio;
RegionList midi; 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) { if (aret < 0) {
abort_reversible_command (); abort_reversible_command ();
return aret; return aret;
@ -111,8 +111,14 @@ Editor::time_stretch (RegionSelection& regions, Temporal::ratio_t const & ratio)
MidiStretch stretch (*_session, request); MidiStretch stretch (*_session, request);
stretch.run (*i); stretch.run (*i);
playlist->replace_region (regions.front()->region(), stretch.results[0], timepos_t newpos;
regions.front()->region()->position());
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); midi_playlists_affected.insert (playlist);
} }
@ -144,7 +150,7 @@ Editor::pitch_shift (RegionSelection& regions, float fraction)
begin_reversible_command (_("pitch shift")); begin_reversible_command (_("pitch shift"));
int ret = time_fx (rl, fraction, true); int ret = time_fx (rl, fraction, true, false);
if (ret > 0) { if (ret > 0) {
commit_reversible_command (); commit_reversible_command ();
@ -159,7 +165,7 @@ Editor::pitch_shift (RegionSelection& regions, float fraction)
* @param pitching true to pitch shift, false to time stretch. * @param pitching true to pitch shift, false to time stretch.
* @return -1 in case of error, otherwise number of regions processed */ * @return -1 in case of error, otherwise number of regions processed */
int 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; 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 timecnt_t newlen = regions.front()->length().scale (ratio);
const timepos_t pos = regions.front()->position (); 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; current_timefx->regions = regions;
switch (current_timefx->run ()) { switch (current_timefx->run ()) {
@ -355,7 +361,7 @@ Editor::time_fx (RegionList& regions, Temporal::ratio_t ratio, bool pitching)
} }
void void
Editor::do_timefx () Editor::do_timefx (bool fixed_end)
{ {
typedef std::map<std::shared_ptr<Region>, std::shared_ptr<Region> > ResultMap; typedef std::map<std::shared_ptr<Region>, std::shared_ptr<Region> > ResultMap;
ResultMap results; ResultMap results;
@ -426,7 +432,11 @@ Editor::do_timefx ()
std::shared_ptr<Playlist> playlist = region->playlist(); std::shared_ptr<Playlist> playlist = region->playlist();
playlist->clear_changes (); playlist->clear_changes ();
if (fixed_end)
playlist->replace_region (region, new_region, region->end ().earlier(new_region->length ()));
else
playlist->replace_region (region, new_region, region->position()); playlist->replace_region (region, new_region, region->position());
PBD::StatefulDiffCommand* cmd = new StatefulDiffCommand (playlist); PBD::StatefulDiffCommand* cmd = new StatefulDiffCommand (playlist);
_session->add_command (cmd); _session->add_command (cmd);
if (!cmd->empty ()) { if (!cmd->empty ()) {
@ -449,7 +459,7 @@ Editor::timefx_thread (void *arg)
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0); 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 /* 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 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 Gtk;
using namespace Gtkmm2ext; 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")) : ArdourDialog (X_("time fx dialog"))
, editor (e) , editor (e)
, pitching (pitch) , pitching (pitch)
@ -64,6 +64,7 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim
, stretch_opts_label (_("Contents")) , stretch_opts_label (_("Contents"))
, precise_button (_("Minimize time distortion")) , precise_button (_("Minimize time distortion"))
, preserve_formants_button(_("Preserve Formants")) , preserve_formants_button(_("Preserve Formants"))
, fixed_end (fixed_end)
, original_length (oldlen) , original_length (oldlen)
, pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0) , 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) , 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: public:
/* We need a position so that BBT mode in the clock can function */ /* 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; ARDOUR::TimeFXRequest request;
Editor& editor; Editor& editor;
@ -65,6 +65,7 @@ public:
Gtk::Button* action_button; Gtk::Button* action_button;
Gtk::VBox packer; Gtk::VBox packer;
int status; int status;
bool fixed_end;
sigc::connection first_cancel; sigc::connection first_cancel;
sigc::connection first_delete; sigc::connection first_delete;