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.
27 #include "pbd/error.h"
28 #include "pbd/pthread_utils.h"
29 #include "pbd/memento_command.h"
30 #include "pbd/stateful_diff_command.h"
32 #include <gtkmm2ext/utils.h>
34 #include "audio_region_view.h"
35 #include "audio_time_axis.h"
37 #include "region_selection.h"
38 #include "time_fx_dialog.h"
40 #include "ardour/session.h"
41 #include "ardour/region.h"
42 #include "ardour/audioplaylist.h"
43 #include "ardour/audio_track.h"
44 #include "ardour/audioregion.h"
45 #include "ardour/stretch.h"
46 #include "ardour/midi_stretch.h"
47 #include "ardour/pitch.h"
50 #include "rubberband/RubberBandStretcher.h"
51 using namespace RubberBand
;
57 using namespace ARDOUR
;
60 using namespace Gtkmm2ext
;
62 /** @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
64 Editor::time_stretch (RegionSelection
& regions
, float fraction
)
66 // FIXME: kludge, implement stretching of selection of both types
68 if (regions
.front()->region()->data_type() == DataType::AUDIO
) {
69 // Audio, pop up timefx dialog
70 return time_fx (regions
, fraction
, false);
73 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (®ions
.front()->get_time_axis_view());
77 boost::shared_ptr
<Playlist
> playlist
= rtv
->track()->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 playlist
->clear_changes ();
85 playlist
->replace_region (regions
.front()->region(), stretch
.results
[0],
86 regions
.front()->region()->position());
87 _session
->add_command (new StatefulDiffCommand (playlist
));
88 commit_reversible_command ();
95 Editor::pitch_shift (RegionSelection
& regions
, float fraction
)
97 return time_fx (regions
, fraction
, true);
100 /** @param val Percentage to time stretch by; ignored if pitch-shifting.
101 * @param pitching true to pitch shift, false to time stretch.
102 * @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
104 Editor::time_fx (RegionSelection
& regions
, float val
, bool pitching
)
106 delete current_timefx
;
108 current_timefx
= new TimeFXDialog (*this, pitching
);
110 current_timefx
->progress_bar
.set_fraction (0.0f
);
112 switch (current_timefx
->run ()) {
113 case RESPONSE_ACCEPT
:
116 current_timefx
->hide ();
120 current_timefx
->status
= 0;
121 current_timefx
->regions
= regions
;
125 float cents
= current_timefx
->pitch_octave_adjustment
.get_value() * 1200.0;
126 float pitch_fraction
;
127 cents
+= current_timefx
->pitch_semitone_adjustment
.get_value() * 100.0;
128 cents
+= current_timefx
->pitch_cent_adjustment
.get_value();
131 // user didn't change anything
132 current_timefx
->hide ();
136 // one octave == 1200 cents
137 // adding one octave doubles the frequency
138 // ratio is 2^^octaves
140 pitch_fraction
= pow(2, cents
/1200);
142 current_timefx
->request
.time_fraction
= 1.0;
143 current_timefx
->request
.pitch_fraction
= pitch_fraction
;
147 current_timefx
->request
.time_fraction
= val
;
148 current_timefx
->request
.pitch_fraction
= 1.0;
152 #ifdef USE_RUBBERBAND
155 RubberBandStretcher::Options options
= 0;
157 bool realtime
= false;
158 bool precise
= false;
159 bool peaklock
= true;
160 bool longwin
= false;
161 bool shortwin
= false;
162 bool preserve_formants
= false;
167 BandLimitedTransients
,
169 } transients
= Transients
;
171 precise
= current_timefx
->precise_button
.get_active();
172 preserve_formants
= current_timefx
->preserve_formants_button
.get_active();
174 txt
= current_timefx
->stretch_opts_selector
.get_active_text ();
176 for (int i
= 0; i
<= 6; i
++) {
177 if (txt
== rb_opt_strings
[i
]) {
183 switch (rb_current_opt
) {
185 transients
= NoTransients
; peaklock
= false; longwin
= true; shortwin
= false;
188 transients
= NoTransients
; peaklock
= false; longwin
= false; shortwin
= false;
191 transients
= NoTransients
; peaklock
= true; longwin
= false; shortwin
= false;
194 transients
= BandLimitedTransients
; peaklock
= true; longwin
= false; shortwin
= false;
197 transients
= Transients
; peaklock
= false; longwin
= false; shortwin
= true;
200 transients
= NoTransients
;
202 preserve_formants
= false;
203 current_timefx
->request
.pitch_fraction
= 1/val
;
209 transients
= Transients
; peaklock
= true; longwin
= false; shortwin
= false;
213 if (realtime
) options
|= RubberBandStretcher::OptionProcessRealTime
;
214 if (precise
) options
|= RubberBandStretcher::OptionStretchPrecise
;
215 if (preserve_formants
) options
|= RubberBandStretcher::OptionFormantPreserved
;
216 if (!peaklock
) options
|= RubberBandStretcher::OptionPhaseIndependent
;
217 if (longwin
) options
|= RubberBandStretcher::OptionWindowLong
;
218 if (shortwin
) options
|= RubberBandStretcher::OptionWindowShort
;
220 switch (transients
) {
222 options
|= RubberBandStretcher::OptionTransientsSmooth
;
224 case BandLimitedTransients
:
225 options
|= RubberBandStretcher::OptionTransientsMixed
;
228 options
|= RubberBandStretcher::OptionTransientsCrisp
;
232 current_timefx
->request
.opts
= (int) options
;
234 current_timefx
->request
.quick_seek
= current_timefx
->quick_button
.get_active();
235 current_timefx
->request
.antialias
= !current_timefx
->antialias_button
.get_active();
237 current_timefx
->request
.progress
= 0.0f
;
238 current_timefx
->request
.done
= false;
239 current_timefx
->request
.cancel
= false;
241 /* re-connect the cancel button and delete events */
243 current_timefx
->first_cancel
.disconnect();
244 current_timefx
->first_delete
.disconnect();
246 current_timefx
->first_cancel
= current_timefx
->cancel_button
->signal_clicked().connect
247 (sigc::mem_fun (current_timefx
, &TimeFXDialog::cancel_in_progress
));
248 current_timefx
->first_delete
= current_timefx
->signal_delete_event().connect
249 (sigc::mem_fun (current_timefx
, &TimeFXDialog::delete_in_progress
));
251 if (pthread_create_and_store ("timefx", ¤t_timefx
->request
.thread
, timefx_thread
, current_timefx
)) {
252 current_timefx
->hide ();
253 error
<< _("timefx cannot be started - thread creation error") << endmsg
;
257 pthread_detach (current_timefx
->request
.thread
);
259 sigc::connection c
= Glib::signal_timeout().connect (sigc::mem_fun (current_timefx
, &TimeFXDialog::update_progress
), 100);
261 while (!current_timefx
->request
.done
&& !current_timefx
->request
.cancel
) {
262 gtk_main_iteration ();
267 current_timefx
->hide ();
268 return current_timefx
->status
;
272 Editor::do_timefx (TimeFXDialog
& dialog
)
275 boost::shared_ptr
<Playlist
> playlist
;
276 boost::shared_ptr
<Region
> new_region
;
277 bool in_command
= false;
279 for (RegionSelection::iterator i
= dialog
.regions
.begin(); i
!= dialog
.regions
.end(); ) {
280 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*>(*i
);
286 boost::shared_ptr
<AudioRegion
> region (arv
->audio_region());
287 TimeAxisView
* tv
= &(arv
->get_time_axis_view());
288 RouteTimeAxisView
* rtv
;
289 RegionSelection::iterator tmp
;
294 if ((rtv
= dynamic_cast<RouteTimeAxisView
*> (tv
)) == 0) {
299 if ((t
= dynamic_cast<Track
*> (rtv
->route().get())) == 0) {
304 if ((playlist
= t
->playlist()) == 0) {
309 if (dialog
.request
.cancel
) {
310 /* we were cancelled */
317 if (dialog
.pitching
) {
318 fx
= new Pitch (*_session
, dialog
.request
);
320 #ifdef USE_RUBBERBAND
321 fx
= new RBStretch (*_session
, dialog
.request
);
323 fx
= new STStretch (*_session
, dialog
.request
);
327 if (fx
->run (region
)) {
329 dialog
.request
.done
= true;
334 if (!fx
->results
.empty()) {
335 new_region
= fx
->results
.front();
338 _session
->begin_reversible_command (dialog
.pitching
? _("pitch shift") : _("time stretch"));
342 playlist
->clear_changes ();
343 playlist
->replace_region (region
, new_region
, region
->position());
344 _session
->add_command (new StatefulDiffCommand (playlist
));
352 _session
->commit_reversible_command ();
356 dialog
.request
.done
= true;
360 Editor::timefx_thread (void *arg
)
362 SessionEvent::create_per_thread_pool ("timefx events", 64);
364 TimeFXDialog
* tsd
= static_cast<TimeFXDialog
*>(arg
);
366 pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS
, 0);
368 tsd
->editor
.do_timefx (*tsd
);
370 /* GACK! HACK! sleep for a bit so that our request buffer for the GUI
371 event loop doesn't die before any changes we made are processed
375 struct timespec t
= { 2, 0 };