Update export dialog to match the new fancy one from 2.0-ongoing.
[ardour.git] / gtk2_ardour / editor_timefx.cc
1 /*
2     Copyright (C) 2000 Paul Davis 
3
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.
8
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.
13
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.
17
18 */
19
20 #include <iostream>
21 #include <cstdlib>
22 #include <cmath>
23
24 #include <string>
25
26 #include <pbd/error.h>
27 #include <pbd/pthread_utils.h>
28 #include <pbd/memento_command.h>
29
30 #include <gtkmm2ext/window_title.h>
31 #include <gtkmm2ext/utils.h>
32
33 #include "editor.h"
34 #include "audio_time_axis.h"
35 #include "audio_region_view.h"
36 #include "region_selection.h"
37
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>
47
48 #ifdef USE_RUBBERBAND
49 #include <rubberband/RubberBandStretcher.h>
50 using namespace RubberBand;
51 #endif
52
53 #include "i18n.h"
54
55 using namespace std;
56 using namespace ARDOUR;
57 using namespace PBD;
58 using namespace sigc;
59 using namespace Gtk;
60 using namespace Gtkmm2ext;
61
62 Editor::TimeFXDialog::TimeFXDialog (Editor& e, bool pitch)
63         : ArdourDialog (X_("time fx dialog")),
64           editor (e),
65           pitching (pitch),
66           pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0),
67           pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0),
68           pitch_cent_adjustment (0.0, -499.0, 500.0, 5.0, 15.0),
69           pitch_octave_spinner (pitch_octave_adjustment),
70           pitch_semitone_spinner (pitch_semitone_adjustment),
71           pitch_cent_spinner (pitch_cent_adjustment),
72           quick_button (_("Quick but Ugly")),
73           antialias_button (_("Skip Anti-aliasing")),
74           stretch_opts_label (_("Contents:")),
75           precise_button (_("Strict Linear"))
76 {
77         set_modal (true);
78         set_position (Gtk::WIN_POS_MOUSE);
79         set_name (N_("TimeFXDialog"));
80
81         WindowTitle title(Glib::get_application_name());
82         if (pitching) {
83                 title += _("Pitch Shift");
84         } else {
85                 title += _("Time Stretch");
86         }
87         set_title(title.get_string());
88
89         cancel_button = add_button (_("Cancel"), Gtk::RESPONSE_CANCEL);
90
91         get_vbox()->set_spacing (5);
92         get_vbox()->set_border_width (12);
93
94         if (pitching) {
95
96                 upper_button_box.set_spacing (5);
97                 upper_button_box.set_border_width (5);
98                 
99                 Gtk::Label* l;
100
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);
104
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);
108
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);
112
113                 pitch_cent_spinner.set_digits (1);
114
115                 add_button (_("Shift"), Gtk::RESPONSE_ACCEPT);
116
117                 get_vbox()->pack_start (upper_button_box, false, false);
118
119         } else {
120
121 #ifdef USE_RUBBERBAND
122                 opts_box.set_spacing (5);
123                 opts_box.set_border_width (5);
124                 vector<string> strings;
125
126                 set_popdown_strings (stretch_opts_selector, editor.rb_opt_strings);
127                 /* set default */
128                 stretch_opts_selector.set_active_text (editor.rb_opt_strings[4]);
129
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);
133
134                 get_vbox()->pack_start (opts_box, false, false);
135
136 #else
137                 upper_button_box.set_homogeneous (true);
138                 upper_button_box.set_spacing (5);
139                 upper_button_box.set_border_width (5);
140
141                 upper_button_box.pack_start (quick_button, true, true);
142                 upper_button_box.pack_start (antialias_button, true, true);
143
144                 quick_button.set_name (N_("TimeFXButton"));
145                 antialias_button.set_name (N_("TimeFXButton"));
146
147                 get_vbox()->pack_start (upper_button_box, false, false);
148
149 #endif  
150                 add_button (_("Stretch/Shrink"), Gtk::RESPONSE_ACCEPT);
151         }
152
153         get_vbox()->pack_start (progress_bar);
154
155         progress_bar.set_name (N_("TimeFXProgress"));
156
157         show_all_children ();
158 }
159
160 gint
161 Editor::TimeFXDialog::update_progress ()
162 {
163         progress_bar.set_fraction (request.progress);
164         return !request.done;
165 }
166
167 void
168 Editor::TimeFXDialog::cancel_in_progress ()
169 {
170         status = -2;
171         request.cancel = true;
172         first_cancel.disconnect();
173 }
174
175 gint
176 Editor::TimeFXDialog::delete_in_progress (GdkEventAny* ev)
177 {
178         status = -2;
179         request.cancel = true;
180         first_delete.disconnect();
181         return TRUE;
182 }
183
184 int
185 Editor::time_stretch (RegionSelection& regions, float fraction)
186 {
187         // FIXME: kludge, implement stretching of selection of both types
188         
189         if (regions.front()->region()->data_type() == DataType::AUDIO) {
190                 // Audio, pop up timefx dialog
191                 return time_fx (regions, fraction, false);
192         } else {
193                 // MIDI, just stretch
194                 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&regions.front()->get_time_axis_view());
195                 if (!rtv)
196                         return -1;
197                 
198                 boost::shared_ptr<Playlist> playlist
199                         = rtv->track()->diskstream()->playlist();
200
201             ARDOUR::TimeFXRequest request;
202                 request.time_fraction = fraction;
203                 MidiStretch stretch(*session, request);
204                 begin_reversible_command ("midi stretch");
205                 stretch.run(regions.front()->region());
206                 XMLNode &before = playlist->get_state();
207                 playlist->replace_region (regions.front()->region(), stretch.results[0],
208                                 regions.front()->region()->position());
209                 XMLNode &after = playlist->get_state();
210                 session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
211                 commit_reversible_command ();
212         }
213
214         return 0;
215 }
216
217 int
218 Editor::pitch_shift (RegionSelection& regions, float fraction)
219 {
220         return time_fx (regions, fraction, true);
221 }
222
223 int
224 Editor::time_fx (RegionSelection& regions, float val, bool pitching)
225 {
226         if (current_timefx != 0) {
227                 delete current_timefx;
228         }
229
230         current_timefx = new TimeFXDialog (*this, pitching);
231
232         current_timefx->progress_bar.set_fraction (0.0f);
233
234         switch (current_timefx->run ()) {
235         case RESPONSE_ACCEPT:
236                 break;
237         default:
238                 current_timefx->hide ();
239                 return 1;
240         }
241
242         current_timefx->status = 0;
243         current_timefx->regions = regions;
244
245         if (pitching) {
246
247                 float cents = current_timefx->pitch_octave_adjustment.get_value() * 1200.0;
248                 float pitch_fraction;
249                 cents += current_timefx->pitch_semitone_adjustment.get_value() * 100.0;
250                 cents += current_timefx->pitch_cent_adjustment.get_value();
251
252                 if (cents == 0.0) {
253                         // user didn't change anything
254                         current_timefx->hide ();
255                         return 0;
256                 }
257
258                 // one octave == 1200 cents
259                 // adding one octave doubles the frequency
260                 // ratio is 2^^octaves
261                                 
262                 pitch_fraction = pow(2, cents/1200);
263
264                 current_timefx->request.time_fraction = 1.0;
265                 current_timefx->request.pitch_fraction = pitch_fraction;
266                 
267         } else {
268
269                 current_timefx->request.time_fraction = val;
270                 current_timefx->request.pitch_fraction = 1.0;
271
272         }
273
274 #ifdef USE_RUBBERBAND
275         /* parse options */
276
277         RubberBandStretcher::Options options = 0;
278
279         bool realtime = false;
280         bool precise = false;
281         bool peaklock = true;
282         bool softening = true;
283         bool longwin = false;
284         bool shortwin = false;
285         string txt;
286
287         enum {
288                 NoTransients,
289                 BandLimitedTransients,
290                 Transients
291         } transients = Transients;
292         
293         precise = current_timefx->precise_button.get_active();
294         
295         txt = current_timefx->stretch_opts_selector.get_active_text ();
296
297         if (txt == rb_opt_strings[0]) {
298                 transients = NoTransients; peaklock = false; longwin = true; shortwin = false; 
299         } else if (txt == rb_opt_strings[1]) {
300                 transients = NoTransients; peaklock = false; longwin = false; shortwin = false; 
301         } else if (txt == rb_opt_strings[2]) {
302                 transients = NoTransients; peaklock = true; longwin = false; shortwin = false; 
303         } else if (txt == rb_opt_strings[3]) {
304                 transients = BandLimitedTransients; peaklock = true; longwin = false; shortwin = false; 
305         } else if (txt == rb_opt_strings[5]) {
306                 transients = Transients; peaklock = false; longwin = false; shortwin = true; 
307         } else {
308                 /* default/4 */
309
310                 transients = Transients; peaklock = true; longwin = false; shortwin = false; 
311         };
312
313
314         if (realtime)    options |= RubberBandStretcher::OptionProcessRealTime;
315         if (precise)     options |= RubberBandStretcher::OptionStretchPrecise;
316         if (!peaklock)   options |= RubberBandStretcher::OptionPhaseIndependent;
317         if (!softening)  options |= RubberBandStretcher::OptionPhasePeakLocked;
318         if (longwin)     options |= RubberBandStretcher::OptionWindowLong;
319         if (shortwin)    options |= RubberBandStretcher::OptionWindowShort;
320                 
321         switch (transients) {
322         case NoTransients:
323                 options |= RubberBandStretcher::OptionTransientsSmooth;
324                 break;
325         case BandLimitedTransients:
326                 options |= RubberBandStretcher::OptionTransientsMixed;
327                 break;
328         case Transients:
329                 options |= RubberBandStretcher::OptionTransientsCrisp;
330                 break;
331         }
332
333         current_timefx->request.opts = (int) options;
334 #else
335         current_timefx->request.quick_seek = current_timefx->quick_button.get_active();
336         current_timefx->request.antialias = !current_timefx->antialias_button.get_active();
337 #endif
338         current_timefx->request.progress = 0.0f;
339         current_timefx->request.done = false;
340         current_timefx->request.cancel = false;
341         
342         /* re-connect the cancel button and delete events */
343         
344         current_timefx->first_cancel.disconnect();
345         current_timefx->first_delete.disconnect();
346         
347         current_timefx->first_cancel = current_timefx->cancel_button->signal_clicked().connect 
348                 (mem_fun (current_timefx, &TimeFXDialog::cancel_in_progress));
349         current_timefx->first_delete = current_timefx->signal_delete_event().connect 
350                 (mem_fun (current_timefx, &TimeFXDialog::delete_in_progress));
351
352         if (pthread_create_and_store ("timefx", &current_timefx->request.thread, 0, timefx_thread, current_timefx)) {
353                 current_timefx->hide ();
354                 error << _("timefx cannot be started - thread creation error") << endmsg;
355                 return -1;
356         }
357
358         pthread_detach (current_timefx->request.thread);
359
360         sigc::connection c = Glib::signal_timeout().connect (mem_fun (current_timefx, &TimeFXDialog::update_progress), 100);
361
362         while (!current_timefx->request.done && !current_timefx->request.cancel) {
363                 gtk_main_iteration ();
364         }
365
366         c.disconnect ();
367         
368         current_timefx->hide ();
369         return current_timefx->status;
370 }
371
372 void
373 Editor::do_timefx (TimeFXDialog& dialog)
374 {
375         Track*    t;
376         boost::shared_ptr<Playlist> playlist;
377         boost::shared_ptr<Region>   new_region;
378         bool in_command = false;
379         
380         for (RegionSelection::iterator i = dialog.regions.begin(); i != dialog.regions.end(); ) {
381                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*i);
382
383                 if (!arv) {
384                         continue;
385                 }
386
387                 boost::shared_ptr<AudioRegion> region (arv->audio_region());
388                 TimeAxisView* tv = &(arv->get_time_axis_view());
389                 RouteTimeAxisView* rtv;
390                 RegionSelection::iterator tmp;
391                 
392                 tmp = i;
393                 ++tmp;
394
395                 if ((rtv = dynamic_cast<RouteTimeAxisView*> (tv)) == 0) {
396                         i = tmp;
397                         continue;
398                 }
399
400                 if ((t = dynamic_cast<Track*> (rtv->route().get())) == 0) {
401                         i = tmp;
402                         continue;
403                 }
404         
405                 if ((playlist = t->diskstream()->playlist()) == 0) {
406                         i = tmp;
407                         continue;
408                 }
409
410                 if (dialog.request.cancel) {
411                         /* we were cancelled */
412                         dialog.status = 1;
413                         return;
414                 }
415
416                 Filter* fx;
417
418                 if (dialog.pitching) {
419                         fx = new Pitch (*session, dialog.request);
420                 } else {
421                         fx = new Stretch (*session, dialog.request);
422                 }
423
424                 if (fx->run (region)) {
425                         dialog.status = -1;
426                         dialog.request.done = true;
427                         delete fx;
428                         return;
429                 }
430
431                 if (!fx->results.empty()) {
432                         new_region = fx->results.front();
433
434                         if (!in_command) {
435                                 begin_reversible_command (dialog.pitching ? _("pitch shift") : _("time stretch"));
436                                 in_command = true;
437                         }
438
439                         XMLNode &before = playlist->get_state();
440                         playlist->replace_region (region, new_region, region->position());
441                         XMLNode &after = playlist->get_state();
442                         session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
443                 }
444
445                 i = tmp;
446                 delete fx;
447         }
448
449         if (in_command) {
450                 commit_reversible_command ();
451         }
452
453         dialog.status = 0;
454         dialog.request.done = true;
455 }
456
457 void*
458 Editor::timefx_thread (void *arg)
459 {
460         PBD::ThreadCreated (pthread_self(), X_("TimeFX"));
461
462         TimeFXDialog* tsd = static_cast<TimeFXDialog*>(arg);
463
464         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
465
466         tsd->editor.do_timefx (*tsd);
467
468         return 0;
469 }
470