From b806ccf373ef8ebafed907eff63a61463468f517 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 31 May 2022 12:36:49 -0600 Subject: [PATCH] timestretching: fix inaccuracies in generated length The design ignored the ratio computed by the drag interaction, and relied on getting the stretch ratio from the dialog. This truncated the actual ratio, leading to (relatively) small errors in the length of the generated region. Now, if the ratio provided by the drag is not (1/1) (i.e. a single click while in timefx mode) then the percentage stretch spinner is marked insensitive and the stretch ratio is taken from the given ratio. For single clicks, the user can still adjust the percentage as they wish --- gtk2_ardour/editor_drag.cc | 82 ++++++++++++++++++++--------------- gtk2_ardour/editor_timefx.cc | 2 +- gtk2_ardour/time_fx_dialog.cc | 17 ++++++-- gtk2_ardour/time_fx_dialog.h | 3 +- 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 2711fc146c..458a92b2cd 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -5517,48 +5517,58 @@ TimeFXDrag::finished (GdkEvent* event, bool movement_occurred) parameters for the timestretch. */ - ratio_t ratio (1, 1); - - if (movement_occurred) { - - motion (event, false); - + if (_editor->get_selection().regions.empty()) { _primary->get_time_axis_view().hide_timestretch (); - - timepos_t adjusted_pos = adjusted_current_time (event); - - if (adjusted_pos < _primary->region()->position()) { - /* backwards drag of the left edge - not usable */ - return; - } - - 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()); - } else { - ratio = ratio_t (newlen.samples(), _primary->region()->length().samples()); - } - -#ifndef USE_RUBBERBAND - // Soundtouch uses fraction / 100 instead of normal (/ 1) -#warning NUTEMPO timefx request now uses a rational type so this needs revisiting - if (_primary->region()->data_type() == DataType::AUDIO) { - ratio = ((newlen - _primary->region()->length()) / newlen) * 100; - } -#endif + return; } - if (!_editor->get_selection().regions.empty()) { - /* primary will already be included in the selection, and edit - group shared editing will propagate selection across - equivalent regions, so just use the current region - selection. - */ + if (!movement_occurred) { + _primary->get_time_axis_view().hide_timestretch (); - if (_editor->time_stretch (_editor->get_selection().regions, ratio) == -1) { + if (_editor->time_stretch (_editor->get_selection().regions, ratio_t (1, 1)) == -1) { error << _("An error occurred while executing time stretch operation") << endmsg; } + return; + } + + + ratio_t ratio (1, 1); + + motion (event, false); + + _primary->get_time_axis_view().hide_timestretch (); + + timepos_t adjusted_pos = adjusted_current_time (event); + + if (adjusted_pos < _primary->region()->position()) { + /* backwards drag of the left edge - not usable */ + return; + } + + 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()); + } else { + ratio = ratio_t (newlen.samples(), _primary->region()->length().samples()); + } + +#ifndef USE_RUBBERBAND + // Soundtouch uses fraction / 100 instead of normal (/ 1) +#warning NUTEMPO timefx request now uses a rational type so this needs revisiting + if (_primary->region()->data_type() == DataType::AUDIO) { + ratio = ((newlen - _primary->region()->length()) / newlen) * 100; + } +#endif + + /* primary will already be included in the selection, and edit + group shared editing will propagate selection across + equivalent regions, so just use the current region + selection. + */ + + if (_editor->time_stretch (_editor->get_selection().regions, ratio) == -1) { + error << _("An error occurred while executing time stretch operation") << endmsg; } } diff --git a/gtk2_ardour/editor_timefx.cc b/gtk2_ardour/editor_timefx.cc index dfe1022c4f..8b63fd0054 100644 --- a/gtk2_ardour/editor_timefx.cc +++ b/gtk2_ardour/editor_timefx.cc @@ -172,7 +172,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, pos); + current_timefx = new TimeFXDialog (*this, pitching, oldlen, newlen, ratio, pos); current_timefx->regions = regions; switch (current_timefx->run ()) { diff --git a/gtk2_ardour/time_fx_dialog.cc b/gtk2_ardour/time_fx_dialog.cc index 1e27f2f85f..61b62640fc 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, 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) : ArdourDialog (X_("time fx dialog")) , editor (e) , pitching (pitch) @@ -71,6 +71,7 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim , pitch_octave_spinner (pitch_octave_adjustment) , pitch_semitone_spinner (pitch_semitone_adjustment) , pitch_cent_spinner (pitch_cent_adjustment) + , duration_ratio (ratio) , duration_adjustment (100.0, -1000.0, 1000.0, 1.0, 10.0) , duration_clock (0) , ignore_adjustment_change (false) @@ -105,6 +106,8 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim upper_button_box.pack_start (*l, false, false); + /* if the ratio is already set, do not allow adjustment */ + if (pitching) { Table* table = manage (new Table (4, 3, false)); table->set_row_spacings (6); @@ -158,11 +161,15 @@ TimeFXDialog::TimeFXDialog (Editor& e, bool pitch, timecnt_t const & oldlen, tim table->attach (*clock_align, 1, 2, row, row+1, Gtk::AttachOptions (Gtk::EXPAND|Gtk::FILL), Gtk::FILL, 0, 0); row++; - const double fract = (new_length / original_length).to_double(); /* note the *100.0 to convert fract into a percentage */ - duration_adjustment.set_value (fract*100.0); + duration_adjustment.set_value (duration_ratio.to_double() * 100.0); + Gtk::SpinButton* spinner = manage (new Gtk::SpinButton (duration_adjustment, 1.0, 3)); + if (duration_ratio != Temporal::ratio_t (1, 1)) { + spinner->set_sensitive (false); + } + l = manage (new Gtk::Label (_("Percent"))); table->attach (*l, 0, 1, row, row+1, Gtk::FILL, Gtk::FILL, 0, 0); table->attach (*spinner, 1, 2, row, row+1, Gtk::FILL, Gtk::FILL, 0, 0); @@ -266,6 +273,10 @@ TimeFXDialog::get_time_fraction () const return Temporal::ratio_t (1, 1); } + if (duration_ratio != Temporal::ratio_t (1, 1)) { + return duration_ratio; + } + return Temporal::ratio_t (duration_adjustment.get_value(), 100); } diff --git a/gtk2_ardour/time_fx_dialog.h b/gtk2_ardour/time_fx_dialog.h index a31401c0fa..f4a80bd793 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::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); ARDOUR::TimeFXRequest request; Editor& editor; @@ -93,6 +93,7 @@ private: Gtk::SpinButton pitch_octave_spinner; Gtk::SpinButton pitch_semitone_spinner; Gtk::SpinButton pitch_cent_spinner; + Temporal::ratio_t duration_ratio; Gtk::Adjustment duration_adjustment; AudioClock* duration_clock; bool ignore_adjustment_change;