X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_timefx.cc;h=c623a3fe1336737bd354bb02711cba2099ed4821;hb=0a3fc4a87fcdab7f7c1ed95d42ec7ac28c95c3e1;hp=79772090f6b61b86de2d540327c43c750b7dd169;hpb=6f4a92f740b2fd75794489ce58f9348f8adf6bf4;p=ardour.git diff --git a/gtk2_ardour/editor_timefx.cc b/gtk2_ardour/editor_timefx.cc index 79772090f6..c623a3fe13 100644 --- a/gtk2_ardour/editor_timefx.cc +++ b/gtk2_ardour/editor_timefx.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000 Paul Davis + Copyright (C) 2000 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,219 +15,403 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ +#include #include #include - +#include #include +#include -#include -#include +#include "pbd/error.h" +#include "pbd/pthread_utils.h" +#include "pbd/memento_command.h" +#include "pbd/stateful_diff_command.h" + +#include "ardour/audioregion.h" +#include "ardour/midi_stretch.h" +#include "ardour/pitch.h" +#include "ardour/region.h" +#include "ardour/session.h" +#include "ardour/stretch.h" + +#include -#include "editor.h" -#include "audio_time_axis.h" #include "audio_region_view.h" +#include "audio_time_axis.h" +#include "editor.h" #include "region_selection.h" +#include "time_fx_dialog.h" -#include -#include -#include -#include -#include -#include +#ifdef USE_RUBBERBAND +#include +using namespace RubberBand; +#endif -#include "i18n.h" +#include "pbd/i18n.h" +using namespace std; using namespace ARDOUR; using namespace PBD; -using namespace sigc; using namespace Gtk; +using namespace Gtkmm2ext; -Editor::TimeStretchDialog::TimeStretchDialog (Editor& e) - : ArdourDialog ("time stretch dialog"), - editor (e), - quick_button (_("Quick but Ugly")), - antialias_button (_("Skip Anti-aliasing")) +/** @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, float fraction) { - set_modal (true); - set_position (Gtk::WIN_POS_MOUSE); - set_title (_("ardour: timestretch")); - set_name (N_("TimeStretchDialog")); - - get_vbox()->set_spacing (5); - get_vbox()->set_border_width (5); - get_vbox()->pack_start (upper_button_box); - get_vbox()->pack_start (progress_bar); - - upper_button_box.set_homogeneous (true); - upper_button_box.set_spacing (5); - upper_button_box.set_border_width (5); - upper_button_box.pack_start (quick_button, true, true); - upper_button_box.pack_start (antialias_button, true, true); - - action_button = add_button (_("Stretch/Shrink it"), Gtk::RESPONSE_ACCEPT); - cancel_button = add_button (_("Cancel"), Gtk::RESPONSE_CANCEL); - - quick_button.set_name (N_("TimeStretchButton")); - antialias_button.set_name (N_("TimeStretchButton")); - progress_bar.set_name (N_("TimeStretchProgress")); - - show_all_children (); -} + RegionList audio; + RegionList midi; + int aret; -gint -Editor::TimeStretchDialog::update_progress () -{ - progress_bar.set_fraction (request.progress); - return request.running; -} + begin_reversible_command (_("stretch/shrink")); -void -Editor::TimeStretchDialog::cancel_timestretch_in_progress () -{ - status = -2; - request.running = false; + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + if ((*i)->region()->data_type() == DataType::AUDIO) { + audio.push_back ((*i)->region()); + } else if ((*i)->region()->data_type() == DataType::MIDI) { + midi.push_back ((*i)->region()); + } + } + + if ((aret = time_fx (audio, fraction, false)) != 0) { + commit_reversible_command (); + return aret; + } + + set > midi_playlists_affected; + + for (RegionList::iterator i = midi.begin(); i != midi.end(); ++i) { + boost::shared_ptr playlist = (*i)->playlist(); + + if (playlist) { + playlist->clear_changes (); + } + + } + + ARDOUR::TimeFXRequest request; + request.time_fraction = fraction; + + for (RegionList::iterator i = midi.begin(); i != midi.end(); ++i) { + boost::shared_ptr playlist = (*i)->playlist(); + + if (!playlist) { + continue; + } + + MidiStretch stretch (*_session, request); + stretch.run (*i); + + playlist->replace_region (regions.front()->region(), stretch.results[0], + regions.front()->region()->position()); + midi_playlists_affected.insert (playlist); + } + + for (set >::iterator p = midi_playlists_affected.begin(); p != midi_playlists_affected.end(); ++p) { + _session->add_command (new StatefulDiffCommand (*p)); + } + + commit_reversible_command (); + + return 0; } -gint -Editor::TimeStretchDialog::delete_timestretch_in_progress (GdkEventAny* ev) +int +Editor::pitch_shift (RegionSelection& regions, float fraction) { - status = -2; - request.running = false; - return TRUE; + RegionList rl; + + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + rl.push_back ((*i)->region()); + } + + begin_reversible_command (_("pitch shift")); + + int ret = time_fx (rl, fraction, true); + + if (ret == 0) { + commit_reversible_command (); + } else { + abort_reversible_command (); + } + + return ret; } +/** @param val Percentage to time stretch by; ignored if pitch-shifting. + * @param pitching true to pitch shift, false to time stretch. + * @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */ int -Editor::run_timestretch (RegionSelection& regions, float fraction) +Editor::time_fx (RegionList& regions, float val, bool pitching) { - pthread_t thread; - - if (current_timestretch == 0) { - current_timestretch = new TimeStretchDialog (*this); + if (regions.empty()) { + return 0; } - current_timestretch->progress_bar.set_fraction (0.0f); + const samplecnt_t oldlen = (samplecnt_t) (regions.front()->length()); + const samplecnt_t newlen = (samplecnt_t) (regions.front()->length() * val); + const samplecnt_t pos = regions.front()->position (); + + delete current_timefx; + current_timefx = new TimeFXDialog (*this, pitching, oldlen, newlen, pos); + current_timefx->regions = regions; - switch (current_timestretch->run ()) { + switch (current_timefx->run ()) { case RESPONSE_ACCEPT: break; default: - current_timestretch->hide (); + current_timefx->hide (); return 1; } - current_timestretch->status = 0; - current_timestretch->regions = regions; - current_timestretch->request.fraction = fraction; - current_timestretch->request.quick_seek = current_timestretch->quick_button.get_active(); - current_timestretch->request.antialias = !current_timestretch->antialias_button.get_active(); - current_timestretch->request.progress = 0.0f; - current_timestretch->request.running = true; - + current_timefx->status = 0; + current_timefx->request.time_fraction = current_timefx->get_time_fraction (); + current_timefx->request.pitch_fraction = current_timefx->get_pitch_fraction (); + + if (current_timefx->request.time_fraction == 1.0 && + current_timefx->request.pitch_fraction == 1.0) { + /* nothing to do */ + current_timefx->hide (); + return 0; + } + +#ifdef USE_RUBBERBAND + /* parse options */ + + RubberBandStretcher::Options options = 0; + + bool realtime = false; + bool precise = false; + bool peaklock = true; + bool longwin = false; + bool shortwin = false; + bool preserve_formants = false; + string txt; + + enum { + NoTransients, + BandLimitedTransients, + Transients + } transients = Transients; + + precise = current_timefx->precise_button.get_active(); + preserve_formants = current_timefx->preserve_formants_button.get_active(); + + txt = current_timefx->stretch_opts_selector.get_active_text (); + + for (int i = 0; i <= 6; i++) { + if (txt == rb_opt_strings[i]) { + rb_current_opt = i; + break; + } + } + + int rb_mode = rb_current_opt; + + if (pitching /*&& rb_current_opt == 6*/) { + /* The timefx dialog does not show the "stretch_opts_selector" + * when pitch-shifting. So the most recently used option from + * "Time Stretch" would be used (if any). That may even be + * "resample without preserving pitch", which would be invalid. + * + * TODO: also show stretch_opts_selector when pitching (except the option + * to not preserve pitch) and use separate rb_current_opt when pitching. + * + * Actually overhaul this the dialog and processing opts below and use rubberband's + * "Crispness" levels: + * -c 0 equivalent to --no-transients --no-lamination --window-long + * -c 1 equivalent to --detector-soft --no-lamination --window-long (for piano) + * -c 2 equivalent to --no-transients --no-lamination + * -c 3 equivalent to --no-transients + * -c 4 equivalent to --bl-transients + * -c 5 default processing options + * -c 6 equivalent to --no-lamination --window-short (may be good for drums) + */ + rb_mode = 4; + } + + switch (rb_mode) { + case 0: + transients = NoTransients; peaklock = false; longwin = true; shortwin = false; + break; + case 1: + transients = NoTransients; peaklock = false; longwin = false; shortwin = false; + break; + case 2: + transients = NoTransients; peaklock = true; longwin = false; shortwin = false; + break; + case 3: + transients = BandLimitedTransients; peaklock = true; longwin = false; shortwin = false; + break; + case 5: + transients = Transients; peaklock = false; longwin = false; shortwin = true; + break; + case 6: + transients = NoTransients; + precise = true; + preserve_formants = false; + current_timefx->request.pitch_fraction = 1.0 / current_timefx->request.time_fraction; + shortwin = true; + // peaklock = false; + break; + default: + /* default/4 */ + transients = Transients; peaklock = true; longwin = false; shortwin = false; + break; + }; + + if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; + if (precise) options |= RubberBandStretcher::OptionStretchPrecise; + if (preserve_formants) options |= RubberBandStretcher::OptionFormantPreserved; + if (!peaklock) options |= RubberBandStretcher::OptionPhaseIndependent; + if (longwin) options |= RubberBandStretcher::OptionWindowLong; + if (shortwin) options |= RubberBandStretcher::OptionWindowShort; + + switch (transients) { + case NoTransients: + options |= RubberBandStretcher::OptionTransientsSmooth; + break; + case BandLimitedTransients: + options |= RubberBandStretcher::OptionTransientsMixed; + break; + case Transients: + options |= RubberBandStretcher::OptionTransientsCrisp; + break; + } + + current_timefx->request.opts = (int) options; +#else + current_timefx->request.quick_seek = current_timefx->quick_button.get_active(); + current_timefx->request.antialias = !current_timefx->antialias_button.get_active(); +#endif + current_timefx->request.done = false; + current_timefx->request.cancel = false; + /* re-connect the cancel button and delete events */ - - current_timestretch->first_cancel.disconnect(); - current_timestretch->first_delete.disconnect(); - - current_timestretch->cancel_button->signal_clicked().connect (mem_fun (current_timestretch, &TimeStretchDialog::cancel_timestretch_in_progress)); - current_timestretch->signal_delete_event().connect (mem_fun (current_timestretch, &TimeStretchDialog::delete_timestretch_in_progress)); - - if (pthread_create_and_store ("timestretch", &thread, 0, timestretch_thread, current_timestretch)) { - current_timestretch->hide (); - error << _("timestretch cannot be started - thread creation error") << endmsg; + + current_timefx->first_cancel.disconnect(); + current_timefx->first_delete.disconnect(); + + current_timefx->first_cancel = current_timefx->cancel_button->signal_clicked().connect + (sigc::mem_fun (current_timefx, &TimeFXDialog::cancel_in_progress)); + current_timefx->first_delete = current_timefx->signal_delete_event().connect + (sigc::mem_fun (current_timefx, &TimeFXDialog::delete_in_progress)); + + current_timefx->start_updates (); + + if (pthread_create_and_store ("timefx", ¤t_timefx->request.thread, timefx_thread, current_timefx)) { + current_timefx->hide (); + error << _("timefx cannot be started - thread creation error") << endmsg; return -1; } - pthread_detach (thread); - - sigc::connection c = Glib::signal_timeout().connect (mem_fun (current_timestretch, &TimeStretchDialog::update_progress), 100); + pthread_detach (current_timefx->request.thread); - while (current_timestretch->request.running) { + while (!current_timefx->request.done && !current_timefx->request.cancel) { gtk_main_iteration (); } - c.disconnect (); - - current_timestretch->hide (); - return current_timestretch->status; + pthread_join (current_timefx->request.thread, 0); + + current_timefx->hide (); + return current_timefx->status; } void -Editor::do_timestretch (TimeStretchDialog& dialog) +Editor::do_timefx () { - Track* t; - Playlist* playlist; - Region* new_region; + boost::shared_ptr playlist; + boost::shared_ptr new_region; + set > playlists_affected; + uint32_t const N = current_timefx->regions.size (); - for (RegionSelection::iterator i = dialog.regions.begin(); i != dialog.regions.end(); ) { - AudioRegionView* arv = dynamic_cast(*i); - if (!arv) - continue; + for (RegionList::iterator i = current_timefx->regions.begin(); i != current_timefx->regions.end(); ++i) { + boost::shared_ptr playlist = (*i)->playlist(); - AudioRegion& region (arv->audio_region()); - TimeAxisView* tv = &(arv->get_time_axis_view()); - RouteTimeAxisView* rtv; - RegionSelection::iterator tmp; - - cerr << "stretch " << region.name() << endl; + if (playlist) { + playlist->clear_changes (); + } + } - tmp = i; - ++tmp; + for (RegionList::iterator i = current_timefx->regions.begin(); i != current_timefx->regions.end(); ++i) { - if ((rtv = dynamic_cast (tv)) == 0) { - i = tmp; + boost::shared_ptr region = boost::dynamic_pointer_cast (*i); + + if (!region || (playlist = region->playlist()) == 0) { continue; } - if ((t = dynamic_cast (rtv->route().get())) == 0) { - i = tmp; - continue; + if (current_timefx->request.cancel) { + /* we were cancelled */ + /* XXX what to do about playlists already affected ? */ + current_timefx->status = 1; + return; } - - if ((playlist = t->diskstream().playlist()) == 0) { - i = tmp; - continue; + + Filter* fx; + + if (current_timefx->pitching) { + fx = new Pitch (*_session, current_timefx->request); + } else { +#ifdef USE_RUBBERBAND + fx = new RBStretch (*_session, current_timefx->request); +#else + fx = new STStretch (*_session, current_timefx->request); +#endif } - dialog.request.region = ®ion; + current_timefx->descend (1.0 / N); - if (!dialog.request.running) { - /* we were cancelled */ - dialog.status = 1; + if (fx->run (region, current_timefx)) { + current_timefx->status = -1; + current_timefx->request.done = true; + delete fx; return; } - if ((new_region = session->tempoize_region (dialog.request)) == 0) { - dialog.status = -1; - dialog.request.running = false; - return; + if (!fx->results.empty()) { + new_region = fx->results.front(); + + playlist->replace_region (region, new_region, region->position()); + playlists_affected.insert (playlist); } - session->add_undo (playlist->get_memento()); - playlist->replace_region (region, *new_region, region.position()); - session->add_redo_no_execute (playlist->get_memento()); + current_timefx->ascend (); + delete fx; + } - i = tmp; + for (set >::iterator p = playlists_affected.begin(); p != playlists_affected.end(); ++p) { + _session->add_command (new StatefulDiffCommand (*p)); } - dialog.status = 0; - dialog.request.running = false; + current_timefx->status = 0; + current_timefx->request.done = true; } void* -Editor::timestretch_thread (void *arg) +Editor::timefx_thread (void *arg) { - PBD::ThreadCreated (pthread_self(), X_("TimeFX")); + SessionEvent::create_per_thread_pool ("timefx events", 64); - TimeStretchDialog* tsd = static_cast(arg); + TimeFXDialog* tsd = static_cast(arg); pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0); - tsd->editor.do_timestretch (*tsd); + tsd->editor.do_timefx (); + /* 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 + by the GUI ... + */ + +#ifdef PLATFORM_WINDOWS + Glib::usleep(2 * G_USEC_PER_SEC); +#else + struct timespec t = { 2, 0 }; + nanosleep (&t, 0); +#endif return 0; } -