2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include <pbd/error.h>
27 #include <pbd/pthread_utils.h>
28 #include <pbd/memento_command.h>
30 #include <gtkmm2ext/window_title.h>
31 #include <gtkmm2ext/utils.h>
34 #include "audio_time_axis.h"
35 #include "audio_region_view.h"
36 #include "region_selection.h"
38 #include <ardour/session.h>
39 #include <ardour/region.h>
40 #include <ardour/audioplaylist.h>
41 #include <ardour/audio_track.h>
42 #include <ardour/audioregion.h>
43 #include <ardour/audio_diskstream.h>
44 #include <ardour/stretch.h>
45 #include <ardour/pitch.h>
48 #include <rubberband/RubberBandStretcher.h>
49 using namespace RubberBand
;
55 using namespace ARDOUR
;
59 using namespace Gtkmm2ext
;
61 Editor::TimeFXDialog::TimeFXDialog (Editor
& e
, bool pitch
)
62 : ArdourDialog (X_("time fx dialog")),
65 pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0),
66 pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0),
67 pitch_cent_adjustment (0.0, -499.0, 500.0, 5.0, 15.0),
68 pitch_octave_spinner (pitch_octave_adjustment
),
69 pitch_semitone_spinner (pitch_semitone_adjustment
),
70 pitch_cent_spinner (pitch_cent_adjustment
),
71 quick_button (_("Quick but Ugly")),
72 antialias_button (_("Skip Anti-aliasing")),
73 stretch_opts_label (_("Contents:")),
74 precise_button (_("Strict Linear")),
75 preserve_formants_button(_("Preserve Formants"))
78 set_position (Gtk::WIN_POS_MOUSE
);
79 set_name (N_("TimeFXDialog"));
81 WindowTitle
title(Glib::get_application_name());
83 title
+= _("Pitch Shift");
85 title
+= _("Time Stretch");
87 set_title(title
.get_string());
89 cancel_button
= add_button (_("Cancel"), Gtk::RESPONSE_CANCEL
);
91 get_vbox()->set_spacing (5);
92 get_vbox()->set_border_width (12);
96 upper_button_box
.set_spacing (5);
97 upper_button_box
.set_border_width (5);
101 l
= manage (new Label (_("Octaves")));
102 upper_button_box
.pack_start (*l
, false, false);
103 upper_button_box
.pack_start (pitch_octave_spinner
, false, false);
105 l
= manage (new Label (_("Semitones (12TET)")));
106 upper_button_box
.pack_start (*l
, false, false);
107 upper_button_box
.pack_start (pitch_semitone_spinner
, false, false);
109 l
= manage (new Label (_("Cents")));
110 upper_button_box
.pack_start (*l
, false, false);
111 upper_button_box
.pack_start (pitch_cent_spinner
, false, false);
113 pitch_cent_spinner
.set_digits (1);
115 upper_button_box
.pack_start (preserve_formants_button
, false, false);
118 add_button (_("Shift"), Gtk::RESPONSE_ACCEPT
);
120 get_vbox()->pack_start (upper_button_box
, false, false);
124 #ifdef USE_RUBBERBAND
125 opts_box
.set_spacing (5);
126 opts_box
.set_border_width (5);
127 vector
<string
> strings
;
129 set_popdown_strings (stretch_opts_selector
, editor
.rb_opt_strings
);
131 stretch_opts_selector
.set_active_text (editor
.rb_opt_strings
[4]);
133 opts_box
.pack_start (precise_button
, false, false);
134 opts_box
.pack_start (stretch_opts_label
, false, false);
135 opts_box
.pack_start (stretch_opts_selector
, false, false);
137 get_vbox()->pack_start (opts_box
, false, false);
140 upper_button_box
.set_homogeneous (true);
141 upper_button_box
.set_spacing (5);
142 upper_button_box
.set_border_width (5);
144 upper_button_box
.pack_start (quick_button
, true, true);
145 upper_button_box
.pack_start (antialias_button
, true, true);
147 quick_button
.set_name (N_("TimeFXButton"));
148 antialias_button
.set_name (N_("TimeFXButton"));
150 get_vbox()->pack_start (upper_button_box
, false, false);
153 add_button (_("Stretch/Shrink"), Gtk::RESPONSE_ACCEPT
);
156 get_vbox()->pack_start (progress_bar
);
158 progress_bar
.set_name (N_("TimeFXProgress"));
160 show_all_children ();
164 Editor::TimeFXDialog::update_progress ()
166 progress_bar
.set_fraction (request
.progress
);
167 return !request
.done
;
171 Editor::TimeFXDialog::cancel_in_progress ()
174 request
.cancel
= true;
175 first_cancel
.disconnect();
179 Editor::TimeFXDialog::delete_in_progress (GdkEventAny
* ev
)
182 request
.cancel
= true;
183 first_delete
.disconnect();
188 Editor::time_stretch (RegionSelection
& regions
, float fraction
)
190 return time_fx (regions
, fraction
, false);
194 Editor::pitch_shift (RegionSelection
& regions
, float fraction
)
196 return time_fx (regions
, fraction
, true);
200 Editor::time_fx (RegionSelection
& regions
, float val
, bool pitching
)
202 if (current_timefx
!= 0) {
203 delete current_timefx
;
206 current_timefx
= new TimeFXDialog (*this, pitching
);
208 current_timefx
->progress_bar
.set_fraction (0.0f
);
210 switch (current_timefx
->run ()) {
211 case RESPONSE_ACCEPT
:
214 current_timefx
->hide ();
218 current_timefx
->status
= 0;
219 current_timefx
->regions
= regions
;
223 float cents
= current_timefx
->pitch_octave_adjustment
.get_value() * 1200.0;
224 float pitch_fraction
;
225 cents
+= current_timefx
->pitch_semitone_adjustment
.get_value() * 100.0;
226 cents
+= current_timefx
->pitch_cent_adjustment
.get_value();
229 // user didn't change anything
230 current_timefx
->hide ();
234 // one octave == 1200 cents
235 // adding one octave doubles the frequency
236 // ratio is 2^^octaves
238 pitch_fraction
= pow(2, cents
/1200);
240 current_timefx
->request
.time_fraction
= 1.0;
241 current_timefx
->request
.pitch_fraction
= pitch_fraction
;
245 current_timefx
->request
.time_fraction
= val
;
246 current_timefx
->request
.pitch_fraction
= 1.0;
250 #ifdef USE_RUBBERBAND
253 RubberBandStretcher::Options options
= 0;
255 bool realtime
= false;
256 bool precise
= false;
257 bool peaklock
= true;
258 bool longwin
= false;
259 bool shortwin
= false;
260 bool preserve_formants
= false;
265 BandLimitedTransients
,
267 } transients
= Transients
;
269 precise
= current_timefx
->precise_button
.get_active();
270 preserve_formants
= current_timefx
->preserve_formants_button
.get_active();
272 txt
= current_timefx
->stretch_opts_selector
.get_active_text ();
274 if (txt
== rb_opt_strings
[0]) {
275 transients
= NoTransients
; peaklock
= false; longwin
= true; shortwin
= false;
276 } else if (txt
== rb_opt_strings
[1]) {
277 transients
= NoTransients
; peaklock
= false; longwin
= false; shortwin
= false;
278 } else if (txt
== rb_opt_strings
[2]) {
279 transients
= NoTransients
; peaklock
= true; longwin
= false; shortwin
= false;
280 } else if (txt
== rb_opt_strings
[3]) {
281 transients
= BandLimitedTransients
; peaklock
= true; longwin
= false; shortwin
= false;
282 } else if (txt
== rb_opt_strings
[5]) {
283 transients
= Transients
; peaklock
= false; longwin
= false; shortwin
= true;
287 transients
= Transients
; peaklock
= true; longwin
= false; shortwin
= false;
291 if (realtime
) options
|= RubberBandStretcher::OptionProcessRealTime
;
292 if (precise
) options
|= RubberBandStretcher::OptionStretchPrecise
;
293 if (preserve_formants
) options
|= RubberBandStretcher::OptionFormantPreserved
;
295 if (!peaklock
) options
|= RubberBandStretcher::OptionPhaseIndependent
;
296 if (longwin
) options
|= RubberBandStretcher::OptionWindowLong
;
297 if (shortwin
) options
|= RubberBandStretcher::OptionWindowShort
;
301 switch (transients
) {
303 options
|= RubberBandStretcher::OptionTransientsSmooth
;
305 case BandLimitedTransients
:
306 options
|= RubberBandStretcher::OptionTransientsMixed
;
309 options
|= RubberBandStretcher::OptionTransientsCrisp
;
313 current_timefx
->request
.opts
= (int) options
;
315 current_timefx
->request
.quick_seek
= current_timefx
->quick_button
.get_active();
316 current_timefx
->request
.antialias
= !current_timefx
->antialias_button
.get_active();
318 current_timefx
->request
.progress
= 0.0f
;
319 current_timefx
->request
.done
= false;
320 current_timefx
->request
.cancel
= false;
322 /* re-connect the cancel button and delete events */
324 current_timefx
->first_cancel
.disconnect();
325 current_timefx
->first_delete
.disconnect();
327 current_timefx
->first_cancel
= current_timefx
->cancel_button
->signal_clicked().connect
328 (mem_fun (current_timefx
, &TimeFXDialog::cancel_in_progress
));
329 current_timefx
->first_delete
= current_timefx
->signal_delete_event().connect
330 (mem_fun (current_timefx
, &TimeFXDialog::delete_in_progress
));
332 if (pthread_create_and_store ("timefx", ¤t_timefx
->request
.thread
, 0, timefx_thread
, current_timefx
)) {
333 current_timefx
->hide ();
334 error
<< _("timefx cannot be started - thread creation error") << endmsg
;
338 pthread_detach (current_timefx
->request
.thread
);
340 sigc::connection c
= Glib::signal_timeout().connect (mem_fun (current_timefx
, &TimeFXDialog::update_progress
), 100);
342 while (!current_timefx
->request
.done
&& !current_timefx
->request
.cancel
) {
343 gtk_main_iteration ();
348 current_timefx
->hide ();
349 return current_timefx
->status
;
353 Editor::do_timefx (TimeFXDialog
& dialog
)
356 boost::shared_ptr
<Playlist
> playlist
;
357 boost::shared_ptr
<Region
> new_region
;
358 bool in_command
= false;
360 for (RegionSelection::iterator i
= dialog
.regions
.begin(); i
!= dialog
.regions
.end(); ) {
361 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*>(*i
);
367 boost::shared_ptr
<AudioRegion
> region (arv
->audio_region());
368 TimeAxisView
* tv
= &(arv
->get_time_axis_view());
369 RouteTimeAxisView
* rtv
;
370 RegionSelection::iterator tmp
;
375 if ((rtv
= dynamic_cast<RouteTimeAxisView
*> (tv
)) == 0) {
380 if ((t
= dynamic_cast<Track
*> (rtv
->route().get())) == 0) {
385 if ((playlist
= t
->diskstream()->playlist()) == 0) {
390 if (dialog
.request
.cancel
) {
391 /* we were cancelled */
398 if (dialog
.pitching
) {
399 fx
= new Pitch (*session
, dialog
.request
);
401 fx
= new Stretch (*session
, dialog
.request
);
404 if (fx
->run (region
)) {
406 dialog
.request
.done
= true;
411 if (!fx
->results
.empty()) {
412 new_region
= fx
->results
.front();
415 begin_reversible_command (dialog
.pitching
? _("pitch shift") : _("time stretch"));
419 XMLNode
&before
= playlist
->get_state();
420 playlist
->replace_region (region
, new_region
, region
->position());
421 XMLNode
&after
= playlist
->get_state();
422 session
->add_command (new MementoCommand
<Playlist
>(*playlist
, &before
, &after
));
430 commit_reversible_command ();
434 dialog
.request
.done
= true;
438 Editor::timefx_thread (void *arg
)
440 PBD::notify_gui_about_thread_creation (pthread_self(), X_("TimeFX"));
442 TimeFXDialog
* tsd
= static_cast<TimeFXDialog
*>(arg
);
444 pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS
, 0);
446 tsd
->editor
.do_timefx (*tsd
);