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"
29 #include "pbd/stateful_diff_command.h"
31 #include <gtkmm2ext/utils.h>
33 #include "audio_region_view.h"
34 #include "audio_time_axis.h"
36 #include "region_selection.h"
37 #include "time_fx_dialog.h"
39 #include "ardour/session.h"
40 #include "ardour/region.h"
41 #include "ardour/audioplaylist.h"
42 #include "ardour/audio_track.h"
43 #include "ardour/audioregion.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
;
59 using namespace Gtkmm2ext
;
61 /** @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
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()->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 /** @return -1 in case of error, 1 if operation was cancelled by the user, 0 if everything went ok */
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 for (int i
= 0; i
<= 6; i
++) {
175 if (txt
== rb_opt_strings
[i
]) {
181 switch (rb_current_opt
) {
183 transients
= NoTransients
; peaklock
= false; longwin
= true; shortwin
= false;
186 transients
= NoTransients
; peaklock
= false; longwin
= false; shortwin
= false;
189 transients
= NoTransients
; peaklock
= true; longwin
= false; shortwin
= false;
192 transients
= BandLimitedTransients
; peaklock
= true; longwin
= false; shortwin
= false;
195 transients
= Transients
; peaklock
= false; longwin
= false; shortwin
= true;
198 transients
= NoTransients
;
200 preserve_formants
= false;
201 current_timefx
->request
.pitch_fraction
= 1/val
;
207 transients
= Transients
; peaklock
= true; longwin
= false; shortwin
= false;
211 if (realtime
) options
|= RubberBandStretcher::OptionProcessRealTime
;
212 if (precise
) options
|= RubberBandStretcher::OptionStretchPrecise
;
213 if (preserve_formants
) options
|= RubberBandStretcher::OptionFormantPreserved
;
214 if (!peaklock
) options
|= RubberBandStretcher::OptionPhaseIndependent
;
215 if (longwin
) options
|= RubberBandStretcher::OptionWindowLong
;
216 if (shortwin
) options
|= RubberBandStretcher::OptionWindowShort
;
218 switch (transients
) {
220 options
|= RubberBandStretcher::OptionTransientsSmooth
;
222 case BandLimitedTransients
:
223 options
|= RubberBandStretcher::OptionTransientsMixed
;
226 options
|= RubberBandStretcher::OptionTransientsCrisp
;
230 current_timefx
->request
.opts
= (int) options
;
232 current_timefx
->request
.quick_seek
= current_timefx
->quick_button
.get_active();
233 current_timefx
->request
.antialias
= !current_timefx
->antialias_button
.get_active();
235 current_timefx
->request
.progress
= 0.0f
;
236 current_timefx
->request
.done
= false;
237 current_timefx
->request
.cancel
= false;
239 /* re-connect the cancel button and delete events */
241 current_timefx
->first_cancel
.disconnect();
242 current_timefx
->first_delete
.disconnect();
244 current_timefx
->first_cancel
= current_timefx
->cancel_button
->signal_clicked().connect
245 (sigc::mem_fun (current_timefx
, &TimeFXDialog::cancel_in_progress
));
246 current_timefx
->first_delete
= current_timefx
->signal_delete_event().connect
247 (sigc::mem_fun (current_timefx
, &TimeFXDialog::delete_in_progress
));
249 if (pthread_create_and_store ("timefx", ¤t_timefx
->request
.thread
, timefx_thread
, current_timefx
)) {
250 current_timefx
->hide ();
251 error
<< _("timefx cannot be started - thread creation error") << endmsg
;
255 pthread_detach (current_timefx
->request
.thread
);
257 sigc::connection c
= Glib::signal_timeout().connect (sigc::mem_fun (current_timefx
, &TimeFXDialog::update_progress
), 100);
259 while (!current_timefx
->request
.done
&& !current_timefx
->request
.cancel
) {
260 gtk_main_iteration ();
265 current_timefx
->hide ();
266 return current_timefx
->status
;
270 Editor::do_timefx (TimeFXDialog
& dialog
)
273 boost::shared_ptr
<Playlist
> playlist
;
274 boost::shared_ptr
<Region
> new_region
;
275 bool in_command
= false;
277 for (RegionSelection::iterator i
= dialog
.regions
.begin(); i
!= dialog
.regions
.end(); ) {
278 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*>(*i
);
284 boost::shared_ptr
<AudioRegion
> region (arv
->audio_region());
285 TimeAxisView
* tv
= &(arv
->get_time_axis_view());
286 RouteTimeAxisView
* rtv
;
287 RegionSelection::iterator tmp
;
292 if ((rtv
= dynamic_cast<RouteTimeAxisView
*> (tv
)) == 0) {
297 if ((t
= dynamic_cast<Track
*> (rtv
->route().get())) == 0) {
302 if ((playlist
= t
->playlist()) == 0) {
307 if (dialog
.request
.cancel
) {
308 /* we were cancelled */
315 if (dialog
.pitching
) {
316 fx
= new Pitch (*_session
, dialog
.request
);
318 #ifdef USE_RUBBERBAND
319 fx
= new RBStretch (*_session
, dialog
.request
);
321 fx
= new STStretch (*_session
, dialog
.request
);
325 if (fx
->run (region
)) {
327 dialog
.request
.done
= true;
332 if (!fx
->results
.empty()) {
333 new_region
= fx
->results
.front();
336 _session
->begin_reversible_command (dialog
.pitching
? _("pitch shift") : _("time stretch"));
340 playlist
->clear_changes ();
341 playlist
->replace_region (region
, new_region
, region
->position());
342 _session
->add_command (new StatefulDiffCommand (playlist
));
350 _session
->commit_reversible_command ();
354 dialog
.request
.done
= true;
358 Editor::timefx_thread (void *arg
)
360 SessionEvent::create_per_thread_pool ("timefx events", 64);
362 TimeFXDialog
* tsd
= static_cast<TimeFXDialog
*>(arg
);
364 pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS
, 0);
366 tsd
->editor
.do_timefx (*tsd
);