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>
32 #include "audio_region_view.h"
33 #include "audio_time_axis.h"
35 #include "region_selection.h"
36 #include "time_fx_dialog.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/midi_stretch.h"
46 #include "ardour/pitch.h"
49 #include "rubberband/RubberBandStretcher.h"
50 using namespace RubberBand
;
56 using namespace ARDOUR
;
60 using namespace Gtkmm2ext
;
63 Editor::time_stretch (RegionSelection
& regions
, float fraction
)
65 // FIXME: kludge, implement stretching of selection of both types
67 if (regions
.front()->region()->data_type() == DataType::AUDIO
) {
68 // Audio, pop up timefx dialog
69 return time_fx (regions
, fraction
, false);
72 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (®ions
.front()->get_time_axis_view());
76 boost::shared_ptr
<Playlist
> playlist
77 = rtv
->track()->diskstream()->playlist();
79 ARDOUR::TimeFXRequest request
;
80 request
.time_fraction
= fraction
;
81 MidiStretch
stretch(*session
, request
);
82 begin_reversible_command ("midi stretch");
83 stretch
.run(regions
.front()->region());
84 XMLNode
&before
= playlist
->get_state();
85 playlist
->replace_region (regions
.front()->region(), stretch
.results
[0],
86 regions
.front()->region()->position());
87 XMLNode
&after
= playlist
->get_state();
88 session
->add_command (new MementoCommand
<Playlist
>(*playlist
, &before
, &after
));
89 commit_reversible_command ();
96 Editor::pitch_shift (RegionSelection
& regions
, float fraction
)
98 return time_fx (regions
, fraction
, true);
102 Editor::time_fx (RegionSelection
& regions
, float val
, bool pitching
)
104 delete current_timefx
;
106 current_timefx
= new TimeFXDialog (*this, pitching
);
108 current_timefx
->progress_bar
.set_fraction (0.0f
);
110 switch (current_timefx
->run ()) {
111 case RESPONSE_ACCEPT
:
114 current_timefx
->hide ();
118 current_timefx
->status
= 0;
119 current_timefx
->regions
= regions
;
123 float cents
= current_timefx
->pitch_octave_adjustment
.get_value() * 1200.0;
124 float pitch_fraction
;
125 cents
+= current_timefx
->pitch_semitone_adjustment
.get_value() * 100.0;
126 cents
+= current_timefx
->pitch_cent_adjustment
.get_value();
129 // user didn't change anything
130 current_timefx
->hide ();
134 // one octave == 1200 cents
135 // adding one octave doubles the frequency
136 // ratio is 2^^octaves
138 pitch_fraction
= pow(2, cents
/1200);
140 current_timefx
->request
.time_fraction
= 1.0;
141 current_timefx
->request
.pitch_fraction
= pitch_fraction
;
145 current_timefx
->request
.time_fraction
= val
;
146 current_timefx
->request
.pitch_fraction
= 1.0;
150 #ifdef USE_RUBBERBAND
153 RubberBandStretcher::Options options
= 0;
155 bool realtime
= false;
156 bool precise
= false;
157 bool peaklock
= true;
158 bool longwin
= false;
159 bool shortwin
= false;
160 bool preserve_formants
= false;
165 BandLimitedTransients
,
167 } transients
= Transients
;
169 precise
= current_timefx
->precise_button
.get_active();
170 preserve_formants
= current_timefx
->preserve_formants_button
.get_active();
172 txt
= current_timefx
->stretch_opts_selector
.get_active_text ();
174 if (txt
== rb_opt_strings
[0]) {
175 transients
= NoTransients
; peaklock
= false; longwin
= true; shortwin
= false;
176 } else if (txt
== rb_opt_strings
[1]) {
177 transients
= NoTransients
; peaklock
= false; longwin
= false; shortwin
= false;
178 } else if (txt
== rb_opt_strings
[2]) {
179 transients
= NoTransients
; peaklock
= true; longwin
= false; shortwin
= false;
180 } else if (txt
== rb_opt_strings
[3]) {
181 transients
= BandLimitedTransients
; peaklock
= true; longwin
= false; shortwin
= false;
182 } else if (txt
== rb_opt_strings
[5]) {
183 transients
= Transients
; peaklock
= false; longwin
= false; shortwin
= true;
187 transients
= Transients
; peaklock
= true; longwin
= false; shortwin
= false;
190 if (realtime
) options
|= RubberBandStretcher::OptionProcessRealTime
;
191 if (precise
) options
|= RubberBandStretcher::OptionStretchPrecise
;
192 if (preserve_formants
) options
|= RubberBandStretcher::OptionFormantPreserved
;
193 if (!peaklock
) options
|= RubberBandStretcher::OptionPhaseIndependent
;
194 if (longwin
) options
|= RubberBandStretcher::OptionWindowLong
;
195 if (shortwin
) options
|= RubberBandStretcher::OptionWindowShort
;
197 switch (transients
) {
199 options
|= RubberBandStretcher::OptionTransientsSmooth
;
201 case BandLimitedTransients
:
202 options
|= RubberBandStretcher::OptionTransientsMixed
;
205 options
|= RubberBandStretcher::OptionTransientsCrisp
;
209 current_timefx
->request
.opts
= (int) options
;
211 current_timefx
->request
.quick_seek
= current_timefx
->quick_button
.get_active();
212 current_timefx
->request
.antialias
= !current_timefx
->antialias_button
.get_active();
214 current_timefx
->request
.progress
= 0.0f
;
215 current_timefx
->request
.done
= false;
216 current_timefx
->request
.cancel
= false;
218 /* re-connect the cancel button and delete events */
220 current_timefx
->first_cancel
.disconnect();
221 current_timefx
->first_delete
.disconnect();
223 current_timefx
->first_cancel
= current_timefx
->cancel_button
->signal_clicked().connect
224 (mem_fun (current_timefx
, &TimeFXDialog::cancel_in_progress
));
225 current_timefx
->first_delete
= current_timefx
->signal_delete_event().connect
226 (mem_fun (current_timefx
, &TimeFXDialog::delete_in_progress
));
228 if (pthread_create_and_store ("timefx", ¤t_timefx
->request
.thread
, 0, timefx_thread
, current_timefx
)) {
229 current_timefx
->hide ();
230 error
<< _("timefx cannot be started - thread creation error") << endmsg
;
234 pthread_detach (current_timefx
->request
.thread
);
236 sigc::connection c
= Glib::signal_timeout().connect (mem_fun (current_timefx
, &TimeFXDialog::update_progress
), 100);
238 while (!current_timefx
->request
.done
&& !current_timefx
->request
.cancel
) {
239 gtk_main_iteration ();
244 current_timefx
->hide ();
245 return current_timefx
->status
;
249 Editor::do_timefx (TimeFXDialog
& dialog
)
252 boost::shared_ptr
<Playlist
> playlist
;
253 boost::shared_ptr
<Region
> new_region
;
254 bool in_command
= false;
256 for (RegionSelection::iterator i
= dialog
.regions
.begin(); i
!= dialog
.regions
.end(); ) {
257 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*>(*i
);
263 boost::shared_ptr
<AudioRegion
> region (arv
->audio_region());
264 TimeAxisView
* tv
= &(arv
->get_time_axis_view());
265 RouteTimeAxisView
* rtv
;
266 RegionSelection::iterator tmp
;
271 if ((rtv
= dynamic_cast<RouteTimeAxisView
*> (tv
)) == 0) {
276 if ((t
= dynamic_cast<Track
*> (rtv
->route().get())) == 0) {
281 if ((playlist
= t
->diskstream()->playlist()) == 0) {
286 if (dialog
.request
.cancel
) {
287 /* we were cancelled */
294 if (dialog
.pitching
) {
295 fx
= new Pitch (*session
, dialog
.request
);
297 #ifdef USE_RUBBERBAND
298 fx
= new RBStretch (*session
, dialog
.request
);
300 fx
= new STStretch (*session
, dialog
.request
);
304 if (fx
->run (region
)) {
306 dialog
.request
.done
= true;
311 if (!fx
->results
.empty()) {
312 new_region
= fx
->results
.front();
315 session
->begin_reversible_command (dialog
.pitching
? _("pitch shift") : _("time stretch"));
319 XMLNode
&before
= playlist
->get_state();
320 playlist
->replace_region (region
, new_region
, region
->position());
321 XMLNode
&after
= playlist
->get_state();
322 session
->add_command (new MementoCommand
<Playlist
>(*playlist
, &before
, &after
));
330 session
->commit_reversible_command ();
334 dialog
.request
.done
= true;
338 Editor::timefx_thread (void *arg
)
340 PBD::notify_gui_about_thread_creation (pthread_self(), X_("TimeFX"));
342 TimeFXDialog
* tsd
= static_cast<TimeFXDialog
*>(arg
);
344 pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS
, 0);
346 tsd
->editor
.do_timefx (*tsd
);