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/utils.h>
33 #include "audio_time_axis.h"
34 #include "audio_region_view.h"
35 #include "region_selection.h"
37 #include <ardour/session.h>
38 #include <ardour/region.h>
39 #include <ardour/audioplaylist.h>
40 #include <ardour/audio_track.h>
41 #include <ardour/audioregion.h>
42 #include <ardour/audio_diskstream.h>
43 #include <ardour/stretch.h>
44 #include <ardour/pitch.h>
47 #include <rubberband/RubberBandStretcher.h>
48 using namespace RubberBand
;
54 using namespace ARDOUR
;
58 using namespace Gtkmm2ext
;
60 Editor::TimeFXDialog::TimeFXDialog (Editor
& e
, bool pitch
)
61 : ArdourDialog (X_("time fx dialog")),
64 pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0),
65 pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0),
66 pitch_cent_adjustment (0.0, -499.0, 500.0, 5.0, 15.0),
67 pitch_octave_spinner (pitch_octave_adjustment
),
68 pitch_semitone_spinner (pitch_semitone_adjustment
),
69 pitch_cent_spinner (pitch_cent_adjustment
),
70 quick_button (_("Quick but Ugly")),
71 antialias_button (_("Skip Anti-aliasing")),
72 stretch_opts_label (_("Contents:")),
73 precise_button (_("Strict Linear")),
74 preserve_formants_button(_("Preserve Formants"))
77 set_position (Gtk::WIN_POS_MOUSE
);
78 set_name (N_("TimeFXDialog"));
81 set_title(_("Pitch Shift"));
83 set_title(_("Time Stretch"));
86 cancel_button
= add_button (_("Cancel"), Gtk::RESPONSE_CANCEL
);
88 get_vbox()->set_spacing (5);
89 get_vbox()->set_border_width (12);
93 upper_button_box
.set_spacing (5);
94 upper_button_box
.set_border_width (5);
98 l
= manage (new Label (_("Octaves")));
99 upper_button_box
.pack_start (*l
, false, false);
100 upper_button_box
.pack_start (pitch_octave_spinner
, false, false);
102 l
= manage (new Label (_("Semitones (12TET)")));
103 upper_button_box
.pack_start (*l
, false, false);
104 upper_button_box
.pack_start (pitch_semitone_spinner
, false, false);
106 l
= manage (new Label (_("Cents")));
107 upper_button_box
.pack_start (*l
, false, false);
108 upper_button_box
.pack_start (pitch_cent_spinner
, false, false);
110 pitch_cent_spinner
.set_digits (1);
112 upper_button_box
.pack_start (preserve_formants_button
, false, false);
115 add_button (_("Shift"), Gtk::RESPONSE_ACCEPT
);
117 get_vbox()->pack_start (upper_button_box
, false, false);
121 #ifdef USE_RUBBERBAND
122 opts_box
.set_spacing (5);
123 opts_box
.set_border_width (5);
124 vector
<string
> strings
;
126 set_popdown_strings (stretch_opts_selector
, editor
.rb_opt_strings
);
128 stretch_opts_selector
.set_active_text (editor
.rb_opt_strings
[4]);
130 opts_box
.pack_start (precise_button
, false, false);
131 opts_box
.pack_start (stretch_opts_label
, false, false);
132 opts_box
.pack_start (stretch_opts_selector
, false, false);
134 get_vbox()->pack_start (opts_box
, false, false);
137 upper_button_box
.set_homogeneous (true);
138 upper_button_box
.set_spacing (5);
139 upper_button_box
.set_border_width (5);
141 upper_button_box
.pack_start (quick_button
, true, true);
142 upper_button_box
.pack_start (antialias_button
, true, true);
144 quick_button
.set_name (N_("TimeFXButton"));
145 antialias_button
.set_name (N_("TimeFXButton"));
147 get_vbox()->pack_start (upper_button_box
, false, false);
150 add_button (_("Stretch/Shrink"), Gtk::RESPONSE_ACCEPT
);
153 get_vbox()->pack_start (progress_bar
);
155 progress_bar
.set_name (N_("TimeFXProgress"));
157 show_all_children ();
161 Editor::TimeFXDialog::update_progress ()
163 progress_bar
.set_fraction (request
.progress
);
164 return !request
.done
;
168 Editor::TimeFXDialog::cancel_in_progress ()
171 request
.cancel
= true;
172 first_cancel
.disconnect();
176 Editor::TimeFXDialog::delete_in_progress (GdkEventAny
* ev
)
179 request
.cancel
= true;
180 first_delete
.disconnect();
185 Editor::time_stretch (RegionSelection
& regions
, float fraction
)
187 return time_fx (regions
, fraction
, false);
191 Editor::pitch_shift (RegionSelection
& regions
, float fraction
)
193 return time_fx (regions
, fraction
, true);
197 Editor::time_fx (RegionSelection
& regions
, float val
, bool pitching
)
199 if (current_timefx
!= 0) {
200 delete current_timefx
;
203 current_timefx
= new TimeFXDialog (*this, pitching
);
205 current_timefx
->progress_bar
.set_fraction (0.0f
);
207 switch (current_timefx
->run ()) {
208 case RESPONSE_ACCEPT
:
211 current_timefx
->hide ();
215 current_timefx
->status
= 0;
216 current_timefx
->regions
= regions
;
220 float cents
= current_timefx
->pitch_octave_adjustment
.get_value() * 1200.0;
221 float pitch_fraction
;
222 cents
+= current_timefx
->pitch_semitone_adjustment
.get_value() * 100.0;
223 cents
+= current_timefx
->pitch_cent_adjustment
.get_value();
226 // user didn't change anything
227 current_timefx
->hide ();
231 // one octave == 1200 cents
232 // adding one octave doubles the frequency
233 // ratio is 2^^octaves
235 pitch_fraction
= pow(2, cents
/1200);
237 current_timefx
->request
.time_fraction
= 1.0;
238 current_timefx
->request
.pitch_fraction
= pitch_fraction
;
242 current_timefx
->request
.time_fraction
= val
;
243 current_timefx
->request
.pitch_fraction
= 1.0;
247 #ifdef USE_RUBBERBAND
250 RubberBandStretcher::Options options
= 0;
252 bool realtime
= false;
253 bool precise
= false;
254 bool peaklock
= true;
255 bool longwin
= false;
256 bool shortwin
= false;
257 bool preserve_formants
= false;
262 BandLimitedTransients
,
264 } transients
= Transients
;
266 precise
= current_timefx
->precise_button
.get_active();
267 preserve_formants
= current_timefx
->preserve_formants_button
.get_active();
269 txt
= current_timefx
->stretch_opts_selector
.get_active_text ();
271 if (txt
== rb_opt_strings
[0]) {
272 transients
= NoTransients
; peaklock
= false; longwin
= true; shortwin
= false;
273 } else if (txt
== rb_opt_strings
[1]) {
274 transients
= NoTransients
; peaklock
= false; longwin
= false; shortwin
= false;
275 } else if (txt
== rb_opt_strings
[2]) {
276 transients
= NoTransients
; peaklock
= true; longwin
= false; shortwin
= false;
277 } else if (txt
== rb_opt_strings
[3]) {
278 transients
= BandLimitedTransients
; peaklock
= true; longwin
= false; shortwin
= false;
279 } else if (txt
== rb_opt_strings
[5]) {
280 transients
= Transients
; peaklock
= false; longwin
= false; shortwin
= true;
284 transients
= Transients
; peaklock
= true; longwin
= false; shortwin
= false;
288 if (realtime
) options
|= RubberBandStretcher::OptionProcessRealTime
;
289 if (precise
) options
|= RubberBandStretcher::OptionStretchPrecise
;
290 if (preserve_formants
) options
|= RubberBandStretcher::OptionFormantPreserved
;
292 if (!peaklock
) options
|= RubberBandStretcher::OptionPhaseIndependent
;
293 if (longwin
) options
|= RubberBandStretcher::OptionWindowLong
;
294 if (shortwin
) options
|= RubberBandStretcher::OptionWindowShort
;
298 switch (transients
) {
300 options
|= RubberBandStretcher::OptionTransientsSmooth
;
302 case BandLimitedTransients
:
303 options
|= RubberBandStretcher::OptionTransientsMixed
;
306 options
|= RubberBandStretcher::OptionTransientsCrisp
;
310 current_timefx
->request
.opts
= (int) options
;
312 current_timefx
->request
.quick_seek
= current_timefx
->quick_button
.get_active();
313 current_timefx
->request
.antialias
= !current_timefx
->antialias_button
.get_active();
315 current_timefx
->request
.progress
= 0.0f
;
316 current_timefx
->request
.done
= false;
317 current_timefx
->request
.cancel
= false;
319 /* re-connect the cancel button and delete events */
321 current_timefx
->first_cancel
.disconnect();
322 current_timefx
->first_delete
.disconnect();
324 current_timefx
->first_cancel
= current_timefx
->cancel_button
->signal_clicked().connect
325 (mem_fun (current_timefx
, &TimeFXDialog::cancel_in_progress
));
326 current_timefx
->first_delete
= current_timefx
->signal_delete_event().connect
327 (mem_fun (current_timefx
, &TimeFXDialog::delete_in_progress
));
329 if (pthread_create_and_store ("timefx", ¤t_timefx
->request
.thread
, 0, timefx_thread
, current_timefx
)) {
330 current_timefx
->hide ();
331 error
<< _("timefx cannot be started - thread creation error") << endmsg
;
335 pthread_detach (current_timefx
->request
.thread
);
337 sigc::connection c
= Glib::signal_timeout().connect (mem_fun (current_timefx
, &TimeFXDialog::update_progress
), 100);
339 while (!current_timefx
->request
.done
&& !current_timefx
->request
.cancel
) {
340 gtk_main_iteration ();
345 current_timefx
->hide ();
346 return current_timefx
->status
;
350 Editor::do_timefx (TimeFXDialog
& dialog
)
353 boost::shared_ptr
<Playlist
> playlist
;
354 boost::shared_ptr
<Region
> new_region
;
355 bool in_command
= false;
357 for (RegionSelection::iterator i
= dialog
.regions
.begin(); i
!= dialog
.regions
.end(); ) {
358 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*>(*i
);
364 boost::shared_ptr
<AudioRegion
> region (arv
->audio_region());
365 TimeAxisView
* tv
= &(arv
->get_time_axis_view());
366 RouteTimeAxisView
* rtv
;
367 RegionSelection::iterator tmp
;
372 if ((rtv
= dynamic_cast<RouteTimeAxisView
*> (tv
)) == 0) {
377 if ((t
= dynamic_cast<Track
*> (rtv
->route().get())) == 0) {
382 if ((playlist
= t
->diskstream()->playlist()) == 0) {
387 if (dialog
.request
.cancel
) {
388 /* we were cancelled */
395 if (dialog
.pitching
) {
396 fx
= new Pitch (*session
, dialog
.request
);
398 fx
= new Stretch (*session
, dialog
.request
);
401 if (fx
->run (region
)) {
403 dialog
.request
.done
= true;
408 if (!fx
->results
.empty()) {
409 new_region
= fx
->results
.front();
412 session
->begin_reversible_command (dialog
.pitching
? _("pitch shift") : _("time stretch"));
416 XMLNode
&before
= playlist
->get_state();
417 playlist
->replace_region (region
, new_region
, region
->position());
418 XMLNode
&after
= playlist
->get_state();
419 session
->add_command (new MementoCommand
<Playlist
>(*playlist
, &before
, &after
));
427 session
->commit_reversible_command ();
431 dialog
.request
.done
= true;
435 Editor::timefx_thread (void *arg
)
437 PBD::notify_gui_about_thread_creation (pthread_self(), X_("TimeFX"));
439 TimeFXDialog
* tsd
= static_cast<TimeFXDialog
*>(arg
);
441 pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS
, 0);
443 tsd
->editor
.do_timefx (*tsd
);